From 363cd274d32f5dd85196390b8285bc17885b1f71 Mon Sep 17 00:00:00 2001
From: Jack Berg <jberg@newrelic.com>
Date: Wed, 4 Oct 2023 17:30:29 -0500
Subject: [PATCH 1/6] Log AnyValue body prototype

---
 .../opentelemetry-sdk-logs.txt                |   3 +
 .../otlp/logs/LogsRequestMarshalerTest.java   |  15 +-
 extensions/incubator/build.gradle.kts         |   2 +
 .../extension/incubator/logs/AnyValue.java    |  50 +++++++
 .../incubator/logs/AnyValueArray.java         |  50 +++++++
 .../incubator/logs/AnyValueBoolean.java       |  39 ++++++
 .../incubator/logs/AnyValueBytes.java         |  47 +++++++
 .../incubator/logs/AnyValueDouble.java        |  39 ++++++
 .../incubator/logs/AnyValueLong.java          |  39 ++++++
 .../incubator/logs/AnyValueString.java        |  42 ++++++
 .../incubator/logs/AnyValueType.java          |  16 +++
 .../logs/ExtendedLogRecordBuilder.java        |  13 ++
 .../extension/incubator/logs/KeyAnyValue.java |  17 +++
 .../incubator/logs/KeyAnyValueImpl.java       |  18 +++
 .../incubator/logs/KeyAnyValueList.java       |  62 +++++++++
 .../incubator/logs/AnyValueTest.java          |  84 ++++++++++++
 sdk/logs/build.gradle.kts                     |   2 +
 .../sdk/logs/SdkLogRecordBuilder.java         |  11 +-
 .../io/opentelemetry/sdk/logs/data/Body.java  |   3 +-
 .../sdk/logs/internal/AnyValueBody.java       |  38 ++++++
 .../sdk/logs/AnyValueBodyTest.java            | 128 ++++++++++++++++++
 21 files changed, 715 insertions(+), 3 deletions(-)
 create mode 100644 extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValue.java
 create mode 100644 extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueArray.java
 create mode 100644 extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueBoolean.java
 create mode 100644 extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueBytes.java
 create mode 100644 extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueDouble.java
 create mode 100644 extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueLong.java
 create mode 100644 extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueString.java
 create mode 100644 extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueType.java
 create mode 100644 extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/ExtendedLogRecordBuilder.java
 create mode 100644 extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/KeyAnyValue.java
 create mode 100644 extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/KeyAnyValueImpl.java
 create mode 100644 extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/KeyAnyValueList.java
 create mode 100644 extensions/incubator/src/test/java/io/opentelemetry/extension/incubator/logs/AnyValueTest.java
 create mode 100644 sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/AnyValueBody.java
 create mode 100644 sdk/logs/src/test/java/io/opentelemetry/sdk/logs/AnyValueBodyTest.java

diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt
index 11010a0b08b..b4ad0cb56f7 100644
--- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt
+++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt
@@ -1,4 +1,7 @@
 Comparing source compatibility of  against 
+***  MODIFIED ENUM: PUBLIC STATIC FINAL io.opentelemetry.sdk.logs.data.Body$Type  (compatible)
+	===  CLASS FILE FORMAT VERSION: 52.0 <- 52.0
+	+++  NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.sdk.logs.data.Body$Type ANY_VALUE
 ***  MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.sdk.logs.ReadWriteLogRecord  (not serializable)
 	===  CLASS FILE FORMAT VERSION: 52.0 <- 52.0
 	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.logs.ReadWriteLogRecord setAllAttributes(io.opentelemetry.api.common.Attributes)
diff --git a/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/logs/LogsRequestMarshalerTest.java b/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/logs/LogsRequestMarshalerTest.java
index 306cfe1ea14..59b60fe2ac7 100644
--- a/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/logs/LogsRequestMarshalerTest.java
+++ b/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/logs/LogsRequestMarshalerTest.java
@@ -24,6 +24,7 @@
 import io.opentelemetry.proto.common.v1.AnyValue;
 import io.opentelemetry.proto.common.v1.InstrumentationScope;
 import io.opentelemetry.proto.common.v1.KeyValue;
+import io.opentelemetry.proto.common.v1.KeyValueList;
 import io.opentelemetry.proto.logs.v1.LogRecord;
 import io.opentelemetry.proto.logs.v1.ResourceLogs;
 import io.opentelemetry.proto.logs.v1.ScopeLogs;
@@ -153,7 +154,19 @@ void toProtoLogRecord_MinimalFields() {
     assertThat(logRecord.getSeverityText()).isBlank();
     assertThat(logRecord.getSeverityNumber().getNumber())
         .isEqualTo(Severity.UNDEFINED_SEVERITY_NUMBER.getSeverityNumber());
-    assertThat(logRecord.getBody()).isEqualTo(AnyValue.newBuilder().setStringValue("").build());
+    assertThat(logRecord.getBody())
+        .isEqualTo(
+            AnyValue.newBuilder()
+                .setKvlistValue(
+                    KeyValueList.newBuilder()
+                        .addValues(
+                            KeyValue.newBuilder()
+                                .setKey("key")
+                                .setValue(AnyValue.newBuilder().setStringValue("foo").build())
+                                .build())
+                        .build())
+                .setStringValue("")
+                .build());
     assertThat(logRecord.getAttributesList()).isEmpty();
     assertThat(logRecord.getDroppedAttributesCount()).isZero();
     assertThat(logRecord.getTimeUnixNano()).isEqualTo(12345);
diff --git a/extensions/incubator/build.gradle.kts b/extensions/incubator/build.gradle.kts
index 6acaf6cddb2..6e79f9361fd 100644
--- a/extensions/incubator/build.gradle.kts
+++ b/extensions/incubator/build.gradle.kts
@@ -12,5 +12,7 @@ otelJava.moduleName.set("io.opentelemetry.extension.incubator")
 dependencies {
   api(project(":api:all"))
 
+  annotationProcessor("com.google.auto.value:auto-value")
+
   testImplementation(project(":sdk:testing"))
 }
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValue.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValue.java
new file mode 100644
index 00000000000..78946fc704a
--- /dev/null
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValue.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.extension.incubator.logs;
+
+import java.util.List;
+import java.util.Map;
+
+public interface AnyValue<T> {
+
+  static AnyValue<String> ofString(String value) {
+    return AnyValueString.create(value);
+  }
+
+  static AnyValue<Boolean> ofBoolean(boolean value) {
+    return AnyValueBoolean.create(value);
+  }
+
+  static AnyValue<Long> ofLong(long value) {
+    return AnyValueLong.create(value);
+  }
+
+  static AnyValue<Double> ofDouble(double value) {
+    return AnyValueDouble.create(value);
+  }
+
+  static AnyValue<byte[]> ofBytes(byte[] value) {
+    return AnyValueBytes.create(value);
+  }
+
+  static AnyValue<List<AnyValue<?>>> ofArray(AnyValue<?>... value) {
+    return AnyValueArray.create(value);
+  }
+
+  static AnyValue<List<KeyAnyValue>> ofKeyAnyValueArray(KeyAnyValue... value) {
+    return KeyAnyValueList.create(value);
+  }
+
+  static AnyValue<List<KeyAnyValue>> ofMap(Map<String, AnyValue<?>> value) {
+    return KeyAnyValueList.createFromMap(value);
+  }
+
+  AnyValueType getType();
+
+  T getValue();
+
+  String asString();
+}
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueArray.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueArray.java
new file mode 100644
index 00000000000..551dbeb5405
--- /dev/null
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueArray.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.extension.incubator.logs;
+
+import static java.util.stream.Collectors.joining;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+final class AnyValueArray implements AnyValue<List<AnyValue<?>>> {
+
+  private final List<AnyValue<?>> value;
+
+  private AnyValueArray(List<AnyValue<?>> value) {
+    this.value = value;
+  }
+
+  static AnyValue<List<AnyValue<?>>> create(AnyValue<?>... value) {
+    Objects.requireNonNull(value, "value");
+    List<AnyValue<?>> list = new ArrayList<>(value.length);
+    list.addAll(Arrays.asList(value));
+    return new AnyValueArray(Collections.unmodifiableList(list));
+  }
+
+  @Override
+  public AnyValueType getType() {
+    return AnyValueType.ARRAY;
+  }
+
+  @Override
+  public List<AnyValue<?>> getValue() {
+    return value;
+  }
+
+  @Override
+  public String asString() {
+    return value.stream().map(AnyValue::asString).collect(joining(", ", "[", "]"));
+  }
+
+  @Override
+  public String toString() {
+    return "AnyValueArray{" + asString() + "}";
+  }
+}
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueBoolean.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueBoolean.java
new file mode 100644
index 00000000000..fe3717a064f
--- /dev/null
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueBoolean.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.extension.incubator.logs;
+
+final class AnyValueBoolean implements AnyValue<Boolean> {
+
+  private final boolean value;
+
+  private AnyValueBoolean(boolean value) {
+    this.value = value;
+  }
+
+  static AnyValue<Boolean> create(boolean value) {
+    return new AnyValueBoolean(value);
+  }
+
+  @Override
+  public AnyValueType getType() {
+    return AnyValueType.BOOLEAN;
+  }
+
+  @Override
+  public Boolean getValue() {
+    return value;
+  }
+
+  @Override
+  public String asString() {
+    return String.valueOf(value);
+  }
+
+  @Override
+  public String toString() {
+    return "AnyValueBoolean{" + asString() + "}";
+  }
+}
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueBytes.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueBytes.java
new file mode 100644
index 00000000000..d2cb9a84ed5
--- /dev/null
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueBytes.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.extension.incubator.logs;
+
+import io.opentelemetry.api.internal.OtelEncodingUtils;
+import java.util.Arrays;
+import java.util.Objects;
+
+final class AnyValueBytes implements AnyValue<byte[]> {
+
+  private final byte[] value;
+
+  private AnyValueBytes(byte[] value) {
+    this.value = value;
+  }
+
+  static AnyValue<byte[]> create(byte[] value) {
+    Objects.requireNonNull(value, "value");
+    return new AnyValueBytes(Arrays.copyOf(value, value.length));
+  }
+
+  @Override
+  public AnyValueType getType() {
+    return AnyValueType.BYTES;
+  }
+
+  @Override
+  public byte[] getValue() {
+    return value;
+  }
+
+  @Override
+  public String asString() {
+    // TODO: base64 would be better, but isn't available in android and java
+    char[] arr = new char[value.length * 2];
+    OtelEncodingUtils.bytesToBase16(value, arr, value.length);
+    return new String(arr);
+  }
+
+  @Override
+  public String toString() {
+    return "AnyValueBytes{" + asString() + "}";
+  }
+}
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueDouble.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueDouble.java
new file mode 100644
index 00000000000..143014f08b2
--- /dev/null
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueDouble.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.extension.incubator.logs;
+
+final class AnyValueDouble implements AnyValue<Double> {
+
+  private final double value;
+
+  private AnyValueDouble(double value) {
+    this.value = value;
+  }
+
+  static AnyValue<Double> create(double value) {
+    return new AnyValueDouble(value);
+  }
+
+  @Override
+  public AnyValueType getType() {
+    return AnyValueType.LONG;
+  }
+
+  @Override
+  public Double getValue() {
+    return value;
+  }
+
+  @Override
+  public String asString() {
+    return String.valueOf(value);
+  }
+
+  @Override
+  public String toString() {
+    return "AnyValueDouble{" + asString() + "}";
+  }
+}
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueLong.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueLong.java
new file mode 100644
index 00000000000..c3c5d258ac3
--- /dev/null
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueLong.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.extension.incubator.logs;
+
+final class AnyValueLong implements AnyValue<Long> {
+
+  private final long value;
+
+  private AnyValueLong(long value) {
+    this.value = value;
+  }
+
+  static AnyValue<Long> create(long value) {
+    return new AnyValueLong(value);
+  }
+
+  @Override
+  public AnyValueType getType() {
+    return AnyValueType.LONG;
+  }
+
+  @Override
+  public Long getValue() {
+    return value;
+  }
+
+  @Override
+  public String asString() {
+    return String.valueOf(value);
+  }
+
+  @Override
+  public String toString() {
+    return "AnyValueLong{" + asString() + "}";
+  }
+}
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueString.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueString.java
new file mode 100644
index 00000000000..1980cec63a7
--- /dev/null
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueString.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.extension.incubator.logs;
+
+import java.util.Objects;
+
+final class AnyValueString implements AnyValue<String> {
+
+  private final String value;
+
+  private AnyValueString(String value) {
+    this.value = value;
+  }
+
+  static AnyValue<String> create(String value) {
+    Objects.requireNonNull(value, "value");
+    return new AnyValueString(value);
+  }
+
+  @Override
+  public AnyValueType getType() {
+    return AnyValueType.STRING;
+  }
+
+  @Override
+  public String getValue() {
+    return value;
+  }
+
+  @Override
+  public String asString() {
+    return value;
+  }
+
+  @Override
+  public String toString() {
+    return "AnyValueString{" + value + "}";
+  }
+}
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueType.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueType.java
new file mode 100644
index 00000000000..3f07849fc06
--- /dev/null
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueType.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.extension.incubator.logs;
+
+public enum AnyValueType {
+  STRING,
+  BOOLEAN,
+  LONG,
+  DOUBLE,
+  ARRAY,
+  KEY_VALUE_LIST,
+  BYTES
+}
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/ExtendedLogRecordBuilder.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/ExtendedLogRecordBuilder.java
new file mode 100644
index 00000000000..3503b1651df
--- /dev/null
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/ExtendedLogRecordBuilder.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.extension.incubator.logs;
+
+import io.opentelemetry.api.logs.LogRecordBuilder;
+
+public interface ExtendedLogRecordBuilder extends LogRecordBuilder {
+
+  LogRecordBuilder setBody(AnyValue<?> body);
+}
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/KeyAnyValue.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/KeyAnyValue.java
new file mode 100644
index 00000000000..8b19b26867f
--- /dev/null
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/KeyAnyValue.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.extension.incubator.logs;
+
+public interface KeyAnyValue {
+
+  static KeyAnyValue of(String key, AnyValue<?> value) {
+    return KeyAnyValueImpl.create(key, value);
+  }
+
+  String getKey();
+
+  AnyValue<?> getAnyValue();
+}
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/KeyAnyValueImpl.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/KeyAnyValueImpl.java
new file mode 100644
index 00000000000..516f46e0840
--- /dev/null
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/KeyAnyValueImpl.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.extension.incubator.logs;
+
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+abstract class KeyAnyValueImpl implements KeyAnyValue {
+
+  KeyAnyValueImpl() {}
+
+  static KeyAnyValueImpl create(String key, AnyValue<?> value) {
+    return new AutoValue_KeyAnyValueImpl(key, value);
+  }
+}
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/KeyAnyValueList.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/KeyAnyValueList.java
new file mode 100644
index 00000000000..28f850641e2
--- /dev/null
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/KeyAnyValueList.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.extension.incubator.logs;
+
+import static java.util.stream.Collectors.joining;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+final class KeyAnyValueList implements AnyValue<List<KeyAnyValue>> {
+
+  private final List<KeyAnyValue> value;
+
+  private KeyAnyValueList(List<KeyAnyValue> value) {
+    this.value = value;
+  }
+
+  static AnyValue<List<KeyAnyValue>> create(KeyAnyValue... value) {
+    Objects.requireNonNull(value, "value");
+    List<KeyAnyValue> list = new ArrayList<>(value.length);
+    list.addAll(Arrays.asList(value));
+    return new KeyAnyValueList(Collections.unmodifiableList(list));
+  }
+
+  static AnyValue<List<KeyAnyValue>> createFromMap(Map<String, AnyValue<?>> value) {
+    Objects.requireNonNull(value, "value");
+    KeyAnyValue[] array =
+        value.entrySet().stream()
+            .map(entry -> KeyAnyValue.of(entry.getKey(), entry.getValue()))
+            .toArray(KeyAnyValue[]::new);
+    return create(array);
+  }
+
+  @Override
+  public AnyValueType getType() {
+    return AnyValueType.KEY_VALUE_LIST;
+  }
+
+  @Override
+  public List<KeyAnyValue> getValue() {
+    return value;
+  }
+
+  @Override
+  public String asString() {
+    return value.stream()
+        .map(entry -> entry.getKey() + "=" + entry.getAnyValue().asString())
+        .collect(joining(", ", "[", "]"));
+  }
+
+  @Override
+  public String toString() {
+    return "KeyAnyValueList{" + asString() + "}";
+  }
+}
diff --git a/extensions/incubator/src/test/java/io/opentelemetry/extension/incubator/logs/AnyValueTest.java b/extensions/incubator/src/test/java/io/opentelemetry/extension/incubator/logs/AnyValueTest.java
new file mode 100644
index 00000000000..333f40d0812
--- /dev/null
+++ b/extensions/incubator/src/test/java/io/opentelemetry/extension/incubator/logs/AnyValueTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.extension.incubator.logs;
+
+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+
+import io.opentelemetry.api.internal.OtelEncodingUtils;
+import java.nio.charset.StandardCharsets;
+import java.util.LinkedHashMap;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class AnyValueTest {
+
+  @ParameterizedTest
+  @MethodSource("asStringArgs")
+  void asString(AnyValue<?> value, String expectedAsString) {
+    assertThat(value.asString()).isEqualTo(expectedAsString);
+  }
+
+  @SuppressWarnings("DoubleBraceInitialization")
+  private static Stream<Arguments> asStringArgs() {
+    return Stream.of(
+        // primitives
+        arguments(AnyValue.ofString("str"), "str"),
+        arguments(AnyValue.ofBoolean(true), "true"),
+        arguments(AnyValue.ofLong(1), "1"),
+        arguments(AnyValue.ofDouble(1.1), "1.1"),
+        // heterogeneous array
+        arguments(
+            AnyValue.ofArray(
+                AnyValue.ofString("str"),
+                AnyValue.ofBoolean(true),
+                AnyValue.ofLong(1),
+                AnyValue.ofDouble(1.1)),
+            "[str, true, 1, 1.1]"),
+        // key value list from KeyAnyValue array
+        arguments(
+            AnyValue.ofKeyAnyValueArray(
+                KeyAnyValue.of("key1", AnyValue.ofString("val1")),
+                KeyAnyValue.of("key2", AnyValue.ofLong(2))),
+            "[key1=val1, key2=2]"),
+        // key value list from map
+        arguments(
+            AnyValue.ofMap(
+                new LinkedHashMap<String, AnyValue<?>>() {
+                  {
+                    put("key1", AnyValue.ofString("val1"));
+                    put("key2", AnyValue.ofLong(2));
+                  }
+                }),
+            "[key1=val1, key2=2]"),
+        // map of map
+        arguments(
+            AnyValue.ofKeyAnyValueArray(
+                KeyAnyValue.of(
+                    "child",
+                    AnyValue.ofKeyAnyValueArray(
+                        KeyAnyValue.of("grandchild", AnyValue.ofString("str"))))),
+            "[child=[grandchild=str]]"),
+        // bytes
+        arguments(
+            AnyValue.ofBytes("hello world".getBytes(StandardCharsets.UTF_8)),
+            "68656c6c6f20776f726c64"));
+  }
+
+  @Test
+  void anyValueByteAsString() {
+    // TODO: add more test cases
+    String str = "hello world";
+    String base16Encoded = AnyValue.ofBytes(str.getBytes(StandardCharsets.UTF_8)).asString();
+    byte[] decodedBytes = OtelEncodingUtils.bytesFromBase16(base16Encoded, base16Encoded.length());
+    assertThat(new String(decodedBytes, StandardCharsets.UTF_8)).isEqualTo(str);
+  }
+
+  // TODO: test equals, hashcode, getType
+}
diff --git a/sdk/logs/build.gradle.kts b/sdk/logs/build.gradle.kts
index 8ffe760b86f..6640c4ed0ee 100644
--- a/sdk/logs/build.gradle.kts
+++ b/sdk/logs/build.gradle.kts
@@ -12,6 +12,7 @@ otelJava.moduleName.set("io.opentelemetry.sdk.logs")
 dependencies {
   api(project(":api:all"))
   api(project(":sdk:common"))
+  implementation(project(":extensions:incubator"))
 
   implementation(project(":api:events"))
 
@@ -20,4 +21,5 @@ dependencies {
   testImplementation(project(":sdk:testing"))
 
   testImplementation("org.awaitility:awaitility")
+  testImplementation("com.google.guava:guava")
 }
diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilder.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilder.java
index 8ef8b2ae4b2..6bdd0407aa5 100644
--- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilder.java
+++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLogRecordBuilder.java
@@ -10,15 +10,18 @@
 import io.opentelemetry.api.logs.Severity;
 import io.opentelemetry.api.trace.Span;
 import io.opentelemetry.context.Context;
+import io.opentelemetry.extension.incubator.logs.AnyValue;
+import io.opentelemetry.extension.incubator.logs.ExtendedLogRecordBuilder;
 import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
 import io.opentelemetry.sdk.internal.AttributesMap;
 import io.opentelemetry.sdk.logs.data.Body;
+import io.opentelemetry.sdk.logs.internal.AnyValueBody;
 import java.time.Instant;
 import java.util.concurrent.TimeUnit;
 import javax.annotation.Nullable;
 
 /** SDK implementation of {@link LogRecordBuilder}. */
-final class SdkLogRecordBuilder implements LogRecordBuilder {
+final class SdkLogRecordBuilder implements ExtendedLogRecordBuilder {
 
   private final LoggerSharedState loggerSharedState;
   private final LogLimits logLimits;
@@ -89,6 +92,12 @@ public SdkLogRecordBuilder setBody(String body) {
     return this;
   }
 
+  @Override
+  public LogRecordBuilder setBody(AnyValue<?> value) {
+    this.body = AnyValueBody.create(value);
+    return this;
+  }
+
   @Override
   public <T> SdkLogRecordBuilder setAttribute(AttributeKey<T> key, T value) {
     if (key == null || key.getKey().isEmpty() || value == null) {
diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/data/Body.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/data/Body.java
index a13ecc003fe..3258ae9c4f0 100644
--- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/data/Body.java
+++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/data/Body.java
@@ -21,7 +21,8 @@ public interface Body {
   /** An enum that represents all the possible value types for an {@code Body}. */
   enum Type {
     EMPTY,
-    STRING
+    STRING,
+    ANY_VALUE
   }
 
   /**
diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/AnyValueBody.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/AnyValueBody.java
new file mode 100644
index 00000000000..4432c392a4d
--- /dev/null
+++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/AnyValueBody.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.sdk.logs.internal;
+
+import io.opentelemetry.extension.incubator.logs.AnyValue;
+import io.opentelemetry.sdk.logs.data.Body;
+import javax.annotation.concurrent.Immutable;
+
+@Immutable
+public final class AnyValueBody implements Body {
+
+  private final AnyValue<?> value;
+
+  private AnyValueBody(AnyValue<?> value) {
+    this.value = value;
+  }
+
+  public static Body create(AnyValue<?> value) {
+    return new AnyValueBody(value);
+  }
+
+  @Override
+  public Type getType() {
+    return Type.ANY_VALUE;
+  }
+
+  @Override
+  public String asString() {
+    return value.asString();
+  }
+
+  public AnyValue<?> getAnyValue() {
+    return value;
+  }
+}
diff --git a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/AnyValueBodyTest.java b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/AnyValueBodyTest.java
new file mode 100644
index 00000000000..5a922107177
--- /dev/null
+++ b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/AnyValueBodyTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.sdk.logs;
+
+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
+
+import io.opentelemetry.api.logs.Logger;
+import io.opentelemetry.extension.incubator.logs.AnyValue;
+import io.opentelemetry.extension.incubator.logs.ExtendedLogRecordBuilder;
+import io.opentelemetry.extension.incubator.logs.KeyAnyValue;
+import io.opentelemetry.sdk.logs.data.Body;
+import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor;
+import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter;
+import java.nio.charset.StandardCharsets;
+import java.util.LinkedHashMap;
+import org.junit.jupiter.api.Test;
+
+class AnyValueBodyTest {
+
+  @Test
+  @SuppressWarnings("DoubleBraceInitialization")
+  void anyValueBody() {
+    InMemoryLogRecordExporter exporter = InMemoryLogRecordExporter.create();
+    SdkLoggerProvider provider =
+        SdkLoggerProvider.builder()
+            .addLogRecordProcessor(SimpleLogRecordProcessor.create(exporter))
+            .build();
+    Logger logger = provider.get(AnyValueBodyTest.class.getName());
+
+    // AnyValue can be a primitive type, like a string, long, double, boolean
+    extendedLogRecordBuilder(logger).setBody(AnyValue.ofLong(1)).emit();
+    assertThat(exporter.getFinishedLogRecordItems())
+        .hasSize(1)
+        .satisfiesExactly(
+            logRecordData -> {
+              assertThat(logRecordData.getBody().getType()).isEqualTo(Body.Type.ANY_VALUE);
+              assertThat(logRecordData.getBody().asString()).isEqualTo("1");
+            });
+    exporter.reset();
+
+    // ...or a byte array of raw data
+    extendedLogRecordBuilder(logger)
+        .setBody(AnyValue.ofBytes("hello world".getBytes(StandardCharsets.UTF_8)))
+        .emit();
+    assertThat(exporter.getFinishedLogRecordItems())
+        .hasSize(1)
+        .satisfiesExactly(
+            logRecordData -> {
+              assertThat(logRecordData.getBody().getType()).isEqualTo(Body.Type.ANY_VALUE);
+              assertThat(logRecordData.getBody().asString()).isEqualTo("68656c6c6f20776f726c64");
+            });
+    exporter.reset();
+
+    // But most commonly it will be used to represent complex structured like a map
+    extendedLogRecordBuilder(logger)
+        .setBody(
+            // The protocol data structure uses a repeated KeyValue to represent a map:
+            // https://github.com/open-telemetry/opentelemetry-proto/blob/ac3242b03157295e4ee9e616af53b81517b06559/opentelemetry/proto/common/v1/common.proto#L59
+            // The comment says that keys aren't allowed to repeat themselves, and because its
+            // represented as a repeated KeyValue, we need to at least offer the ability to preserve
+            // order.
+            // Accepting a Map<String, AnyValue<?>> makes for a cleaner API, but ordering of the
+            // entries is lost. To accommodate use cases where ordering should be preserved we
+            // accept an array of key value pairs, but also a map based alternative (see the
+            // key_value_list_key entry).
+            AnyValue.ofKeyAnyValueArray(
+                KeyAnyValue.of("str_key", AnyValue.ofString("value")),
+                KeyAnyValue.of("bool_key", AnyValue.ofBoolean(true)),
+                KeyAnyValue.of("long_key", AnyValue.ofLong(1L)),
+                KeyAnyValue.of("double_key", AnyValue.ofDouble(1.1)),
+                KeyAnyValue.of(
+                    "bytes_key", AnyValue.ofBytes("bytes".getBytes(StandardCharsets.UTF_8))),
+                KeyAnyValue.of(
+                    "arr_key",
+                    AnyValue.ofArray(
+                        AnyValue.ofString("entry1"), AnyValue.ofLong(2), AnyValue.ofDouble(3.3))),
+                KeyAnyValue.of(
+                    "key_value_list_key",
+                    AnyValue.ofMap(
+                        new LinkedHashMap<String, AnyValue<?>>() {
+                          {
+                            put("child_str_key1", AnyValue.ofString("child_value1"));
+                            put("child_str_key2", AnyValue.ofString("child_value2"));
+                          }
+                        }))))
+        .emit();
+    assertThat(exporter.getFinishedLogRecordItems())
+        .hasSize(1)
+        .satisfiesExactly(
+            logRecordData -> {
+              assertThat(logRecordData.getBody().getType()).isEqualTo(Body.Type.ANY_VALUE);
+              assertThat(logRecordData.getBody().asString())
+                  .isEqualTo(
+                      "["
+                          + "str_key=value, "
+                          + "bool_key=true, "
+                          + "long_key=1, "
+                          + "double_key=1.1, "
+                          + "bytes_key=6279746573, "
+                          + "arr_key=[entry1, 2, 3.3], "
+                          + "key_value_list_key=[child_str_key1=child_value1, child_str_key2=child_value2]"
+                          + "]");
+            });
+    exporter.reset();
+
+    // ..or an array (optionally with heterogeneous types)
+    extendedLogRecordBuilder(logger)
+        .setBody(
+            AnyValue.ofArray(
+                AnyValue.ofString("entry1"), AnyValue.ofString("entry2"), AnyValue.ofLong(3)))
+        .emit();
+    assertThat(exporter.getFinishedLogRecordItems())
+        .hasSize(1)
+        .satisfiesExactly(
+            logRecordData -> {
+              assertThat(logRecordData.getBody().getType()).isEqualTo(Body.Type.ANY_VALUE);
+              assertThat(logRecordData.getBody().asString()).isEqualTo("[entry1, entry2, 3]");
+            });
+    exporter.reset();
+  }
+
+  ExtendedLogRecordBuilder extendedLogRecordBuilder(Logger logger) {
+    return (ExtendedLogRecordBuilder) logger.logRecordBuilder();
+  }
+}

From 9b814235fa0185f8e113b84a1ee4530b4fe878cd Mon Sep 17 00:00:00 2001
From: Jack Berg <jberg@newrelic.com>
Date: Wed, 4 Oct 2023 20:14:44 -0500
Subject: [PATCH 2/6] Improve API ergonomics

---
 .../internal/otlp/logs/LogMarshaler.java      |  2 +-
 .../extension/incubator/logs/AnyValue.java    | 16 ++++----
 .../incubator/logs/AnyValueTest.java          | 39 ++++++++-----------
 .../sdk/logs/AnyValueBodyTest.java            | 30 +++++++-------
 4 files changed, 39 insertions(+), 48 deletions(-)

diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/logs/LogMarshaler.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/logs/LogMarshaler.java
index bd2c6b3b8a2..7a68041e5d1 100644
--- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/logs/LogMarshaler.java
+++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/logs/LogMarshaler.java
@@ -41,7 +41,7 @@ static LogMarshaler create(LogRecordData logRecordData) {
     KeyValueMarshaler[] attributeMarshalers =
         KeyValueMarshaler.createRepeated(logRecordData.getAttributes());
 
-    // For now, map all the bodies to String AnyValue.
+    // TODO(jack-berg): handle AnyValue log body
     StringAnyValueMarshaler anyValueMarshaler =
         new StringAnyValueMarshaler(MarshalerUtil.toBytes(logRecordData.getBody().asString()));
 
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValue.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValue.java
index 78946fc704a..8dc9ed04b97 100644
--- a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValue.java
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValue.java
@@ -10,35 +10,35 @@
 
 public interface AnyValue<T> {
 
-  static AnyValue<String> ofString(String value) {
+  static AnyValue<String> of(String value) {
     return AnyValueString.create(value);
   }
 
-  static AnyValue<Boolean> ofBoolean(boolean value) {
+  static AnyValue<Boolean> of(boolean value) {
     return AnyValueBoolean.create(value);
   }
 
-  static AnyValue<Long> ofLong(long value) {
+  static AnyValue<Long> of(long value) {
     return AnyValueLong.create(value);
   }
 
-  static AnyValue<Double> ofDouble(double value) {
+  static AnyValue<Double> of(double value) {
     return AnyValueDouble.create(value);
   }
 
-  static AnyValue<byte[]> ofBytes(byte[] value) {
+  static AnyValue<byte[]> of(byte[] value) {
     return AnyValueBytes.create(value);
   }
 
-  static AnyValue<List<AnyValue<?>>> ofArray(AnyValue<?>... value) {
+  static AnyValue<List<AnyValue<?>>> of(AnyValue<?>... value) {
     return AnyValueArray.create(value);
   }
 
-  static AnyValue<List<KeyAnyValue>> ofKeyAnyValueArray(KeyAnyValue... value) {
+  static AnyValue<List<KeyAnyValue>> of(KeyAnyValue... value) {
     return KeyAnyValueList.create(value);
   }
 
-  static AnyValue<List<KeyAnyValue>> ofMap(Map<String, AnyValue<?>> value) {
+  static AnyValue<List<KeyAnyValue>> of(Map<String, AnyValue<?>> value) {
     return KeyAnyValueList.createFromMap(value);
   }
 
diff --git a/extensions/incubator/src/test/java/io/opentelemetry/extension/incubator/logs/AnyValueTest.java b/extensions/incubator/src/test/java/io/opentelemetry/extension/incubator/logs/AnyValueTest.java
index 333f40d0812..1c8bf63a575 100644
--- a/extensions/incubator/src/test/java/io/opentelemetry/extension/incubator/logs/AnyValueTest.java
+++ b/extensions/incubator/src/test/java/io/opentelemetry/extension/incubator/logs/AnyValueTest.java
@@ -10,6 +10,7 @@
 
 import io.opentelemetry.api.internal.OtelEncodingUtils;
 import java.nio.charset.StandardCharsets;
+import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.stream.Stream;
 import org.junit.jupiter.api.Test;
@@ -29,53 +30,47 @@ void asString(AnyValue<?> value, String expectedAsString) {
   private static Stream<Arguments> asStringArgs() {
     return Stream.of(
         // primitives
-        arguments(AnyValue.ofString("str"), "str"),
-        arguments(AnyValue.ofBoolean(true), "true"),
-        arguments(AnyValue.ofLong(1), "1"),
-        arguments(AnyValue.ofDouble(1.1), "1.1"),
+        arguments(AnyValue.of("str"), "str"),
+        arguments(AnyValue.of(true), "true"),
+        arguments(AnyValue.of(1), "1"),
+        arguments(AnyValue.of(1.1), "1.1"),
         // heterogeneous array
         arguments(
-            AnyValue.ofArray(
-                AnyValue.ofString("str"),
-                AnyValue.ofBoolean(true),
-                AnyValue.ofLong(1),
-                AnyValue.ofDouble(1.1)),
+            AnyValue.of(AnyValue.of("str"), AnyValue.of(true), AnyValue.of(1), AnyValue.of(1.1)),
             "[str, true, 1, 1.1]"),
         // key value list from KeyAnyValue array
         arguments(
-            AnyValue.ofKeyAnyValueArray(
-                KeyAnyValue.of("key1", AnyValue.ofString("val1")),
-                KeyAnyValue.of("key2", AnyValue.ofLong(2))),
+            AnyValue.of(
+                KeyAnyValue.of("key1", AnyValue.of("val1")),
+                KeyAnyValue.of("key2", AnyValue.of(2))),
             "[key1=val1, key2=2]"),
         // key value list from map
         arguments(
-            AnyValue.ofMap(
+            AnyValue.of(
                 new LinkedHashMap<String, AnyValue<?>>() {
                   {
-                    put("key1", AnyValue.ofString("val1"));
-                    put("key2", AnyValue.ofLong(2));
+                    put("key1", AnyValue.of("val1"));
+                    put("key2", AnyValue.of(2));
                   }
                 }),
             "[key1=val1, key2=2]"),
         // map of map
         arguments(
-            AnyValue.ofKeyAnyValueArray(
-                KeyAnyValue.of(
+            AnyValue.of(
+                Collections.singletonMap(
                     "child",
-                    AnyValue.ofKeyAnyValueArray(
-                        KeyAnyValue.of("grandchild", AnyValue.ofString("str"))))),
+                    AnyValue.of(Collections.singletonMap("grandchild", AnyValue.of("str"))))),
             "[child=[grandchild=str]]"),
         // bytes
         arguments(
-            AnyValue.ofBytes("hello world".getBytes(StandardCharsets.UTF_8)),
-            "68656c6c6f20776f726c64"));
+            AnyValue.of("hello world".getBytes(StandardCharsets.UTF_8)), "68656c6c6f20776f726c64"));
   }
 
   @Test
   void anyValueByteAsString() {
     // TODO: add more test cases
     String str = "hello world";
-    String base16Encoded = AnyValue.ofBytes(str.getBytes(StandardCharsets.UTF_8)).asString();
+    String base16Encoded = AnyValue.of(str.getBytes(StandardCharsets.UTF_8)).asString();
     byte[] decodedBytes = OtelEncodingUtils.bytesFromBase16(base16Encoded, base16Encoded.length());
     assertThat(new String(decodedBytes, StandardCharsets.UTF_8)).isEqualTo(str);
   }
diff --git a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/AnyValueBodyTest.java b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/AnyValueBodyTest.java
index 5a922107177..09486fdaa06 100644
--- a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/AnyValueBodyTest.java
+++ b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/AnyValueBodyTest.java
@@ -31,7 +31,7 @@ void anyValueBody() {
     Logger logger = provider.get(AnyValueBodyTest.class.getName());
 
     // AnyValue can be a primitive type, like a string, long, double, boolean
-    extendedLogRecordBuilder(logger).setBody(AnyValue.ofLong(1)).emit();
+    extendedLogRecordBuilder(logger).setBody(AnyValue.of(1)).emit();
     assertThat(exporter.getFinishedLogRecordItems())
         .hasSize(1)
         .satisfiesExactly(
@@ -43,7 +43,7 @@ void anyValueBody() {
 
     // ...or a byte array of raw data
     extendedLogRecordBuilder(logger)
-        .setBody(AnyValue.ofBytes("hello world".getBytes(StandardCharsets.UTF_8)))
+        .setBody(AnyValue.of("hello world".getBytes(StandardCharsets.UTF_8)))
         .emit();
     assertThat(exporter.getFinishedLogRecordItems())
         .hasSize(1)
@@ -66,24 +66,22 @@ void anyValueBody() {
             // entries is lost. To accommodate use cases where ordering should be preserved we
             // accept an array of key value pairs, but also a map based alternative (see the
             // key_value_list_key entry).
-            AnyValue.ofKeyAnyValueArray(
-                KeyAnyValue.of("str_key", AnyValue.ofString("value")),
-                KeyAnyValue.of("bool_key", AnyValue.ofBoolean(true)),
-                KeyAnyValue.of("long_key", AnyValue.ofLong(1L)),
-                KeyAnyValue.of("double_key", AnyValue.ofDouble(1.1)),
-                KeyAnyValue.of(
-                    "bytes_key", AnyValue.ofBytes("bytes".getBytes(StandardCharsets.UTF_8))),
+            AnyValue.of(
+                KeyAnyValue.of("str_key", AnyValue.of("value")),
+                KeyAnyValue.of("bool_key", AnyValue.of(true)),
+                KeyAnyValue.of("long_key", AnyValue.of(1L)),
+                KeyAnyValue.of("double_key", AnyValue.of(1.1)),
+                KeyAnyValue.of("bytes_key", AnyValue.of("bytes".getBytes(StandardCharsets.UTF_8))),
                 KeyAnyValue.of(
                     "arr_key",
-                    AnyValue.ofArray(
-                        AnyValue.ofString("entry1"), AnyValue.ofLong(2), AnyValue.ofDouble(3.3))),
+                    AnyValue.of(AnyValue.of("entry1"), AnyValue.of(2), AnyValue.of(3.3))),
                 KeyAnyValue.of(
                     "key_value_list_key",
-                    AnyValue.ofMap(
+                    AnyValue.of(
                         new LinkedHashMap<String, AnyValue<?>>() {
                           {
-                            put("child_str_key1", AnyValue.ofString("child_value1"));
-                            put("child_str_key2", AnyValue.ofString("child_value2"));
+                            put("child_str_key1", AnyValue.of("child_value1"));
+                            put("child_str_key2", AnyValue.of("child_value2"));
                           }
                         }))))
         .emit();
@@ -108,9 +106,7 @@ void anyValueBody() {
 
     // ..or an array (optionally with heterogeneous types)
     extendedLogRecordBuilder(logger)
-        .setBody(
-            AnyValue.ofArray(
-                AnyValue.ofString("entry1"), AnyValue.ofString("entry2"), AnyValue.ofLong(3)))
+        .setBody(AnyValue.of(AnyValue.of("entry1"), AnyValue.of("entry2"), AnyValue.of(3)))
         .emit();
     assertThat(exporter.getFinishedLogRecordItems())
         .hasSize(1)

From 057ebbcb6c728c0c0913ba2d21942bd26b060892 Mon Sep 17 00:00:00 2001
From: Jack Berg <jberg@newrelic.com>
Date: Fri, 13 Oct 2023 15:54:57 -0500
Subject: [PATCH 3/6] Clean up to prepare for review

---
 .../kotlin/otel.java-conventions.gradle.kts   |   5 +-
 .../opentelemetry-sdk-common.txt              |  11 +-
 .../opentelemetry-sdk-logs.txt                |   3 -
 .../opentelemetry-sdk-metrics.txt             |  21 +--
 .../opentelemetry-sdk-testing.txt             |  12 +-
 .../extension/incubator/logs/AnyValue.java    |  54 +++++++
 .../incubator/logs/AnyValueArray.java         |  15 +-
 .../incubator/logs/AnyValueBoolean.java       |  15 ++
 .../incubator/logs/AnyValueBytes.java         |  20 ++-
 .../incubator/logs/AnyValueDouble.java        |  17 ++-
 .../incubator/logs/AnyValueLong.java          |  15 ++
 .../incubator/logs/AnyValueString.java        |  15 +-
 .../incubator/logs/AnyValueType.java          |   5 +
 .../logs/ExtendedLogRecordBuilder.java        |   2 +
 .../extension/incubator/logs/KeyAnyValue.java |   8 +
 .../incubator/logs/KeyAnyValueList.java       |  17 ++-
 .../incubator/logs/AnyValueTest.java          | 141 +++++++++++++++++-
 .../sdk/logs/internal/AnyValueBody.java       |   5 +
 .../sdk/logs/AnyValueBodyTest.java            |  29 ++++
 19 files changed, 353 insertions(+), 57 deletions(-)

diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts
index 43791644017..4d601c406dc 100644
--- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts
+++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts
@@ -74,10 +74,7 @@ tasks {
             // https://groups.google.com/forum/#!topic/bazel-discuss/_R3A9TJSoPM
             "-Xlint:-processing",
             // We suppress the "options" warning because it prevents compilation on modern JDKs
-            "-Xlint:-options",
-
-            // Fail build on any warning
-            "-Werror",
+            "-Xlint:-options"
           ),
         )
       }
diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt
index cc95503822e..df26146497b 100644
--- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt
+++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt
@@ -1,11 +1,2 @@
 Comparing source compatibility of  against 
-+++  NEW ENUM: PUBLIC(+) FINAL(+) io.opentelemetry.sdk.common.export.MemoryMode  (compatible)
-	+++  CLASS FILE FORMAT VERSION: 52.0 <- n.a.
-	+++  NEW INTERFACE: java.lang.constant.Constable
-	+++  NEW INTERFACE: java.lang.Comparable
-	+++  NEW INTERFACE: java.io.Serializable
-	+++  NEW SUPERCLASS: java.lang.Enum
-	+++  NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.sdk.common.export.MemoryMode REUSABLE_DATA
-	+++  NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.sdk.common.export.MemoryMode IMMUTABLE_DATA
-	+++  NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.common.export.MemoryMode valueOf(java.lang.String)
-	+++  NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.common.export.MemoryMode[] values()
+No changes.
\ No newline at end of file
diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt
index b4ad0cb56f7..fac1cdbcecc 100644
--- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt
+++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt
@@ -2,6 +2,3 @@ Comparing source compatibility of  against
 ***  MODIFIED ENUM: PUBLIC STATIC FINAL io.opentelemetry.sdk.logs.data.Body$Type  (compatible)
 	===  CLASS FILE FORMAT VERSION: 52.0 <- 52.0
 	+++  NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.sdk.logs.data.Body$Type ANY_VALUE
-***  MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.sdk.logs.ReadWriteLogRecord  (not serializable)
-	===  CLASS FILE FORMAT VERSION: 52.0 <- 52.0
-	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.logs.ReadWriteLogRecord setAllAttributes(io.opentelemetry.api.common.Attributes)
diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt
index 6cf519e2823..df26146497b 100644
--- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt
+++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-metrics.txt
@@ -1,21 +1,2 @@
 Comparing source compatibility of  against 
-***  MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.sdk.metrics.export.CollectionRegistration  (not serializable)
-	===  CLASS FILE FORMAT VERSION: 52.0 <- 52.0
-	+++  NEW METHOD: PUBLIC(+) java.util.Collection<io.opentelemetry.sdk.metrics.data.MetricData> collectAllMetrics()
-	+++  NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.metrics.export.CollectionRegistration noop()
-***  MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.sdk.metrics.export.MetricExporter  (not serializable)
-	===  CLASS FILE FORMAT VERSION: 52.0 <- 52.0
-	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.common.export.MemoryMode getMemoryMode()
-+++  NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.sdk.metrics.export.MetricProducer  (not serializable)
-	+++  CLASS FILE FORMAT VERSION: 52.0 <- n.a.
-	+++  NEW SUPERCLASS: java.lang.Object
-	+++  NEW METHOD: PUBLIC(+) ABSTRACT(+) java.util.Collection<io.opentelemetry.sdk.metrics.data.MetricData> produce(io.opentelemetry.sdk.resources.Resource)
-***  MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.sdk.metrics.export.MetricReader  (not serializable)
-	===  CLASS FILE FORMAT VERSION: 52.0 <- 52.0
-	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.common.export.MemoryMode getMemoryMode()
-***  MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.metrics.export.PeriodicMetricReader  (not serializable)
-	===  CLASS FILE FORMAT VERSION: 52.0 <- 52.0
-	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.common.export.MemoryMode getMemoryMode()
-***  MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder  (not serializable)
-	===  CLASS FILE FORMAT VERSION: 52.0 <- 52.0
-	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder registerMetricProducer(io.opentelemetry.sdk.metrics.export.MetricProducer)
+No changes.
\ No newline at end of file
diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-testing.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-testing.txt
index 24ed11618d8..df26146497b 100644
--- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-testing.txt
+++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-testing.txt
@@ -1,12 +1,2 @@
 Comparing source compatibility of  against 
-***  MODIFIED CLASS: PUBLIC io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader  (not serializable)
-	===  CLASS FILE FORMAT VERSION: 52.0 <- 52.0
-	+++  NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.testing.exporter.InMemoryMetricReaderBuilder builder()
-	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.common.export.MemoryMode getMemoryMode()
-+++  NEW CLASS: PUBLIC(+) FINAL(+) io.opentelemetry.sdk.testing.exporter.InMemoryMetricReaderBuilder  (not serializable)
-	+++  CLASS FILE FORMAT VERSION: 52.0 <- n.a.
-	+++  NEW SUPERCLASS: java.lang.Object
-	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader build()
-	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.exporter.InMemoryMetricReaderBuilder setAggregationTemporalitySelector(io.opentelemetry.sdk.metrics.export.AggregationTemporalitySelector)
-	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.exporter.InMemoryMetricReaderBuilder setDefaultAggregationSelector(io.opentelemetry.sdk.metrics.export.DefaultAggregationSelector)
-	+++  NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.exporter.InMemoryMetricReaderBuilder setMemoryMode(io.opentelemetry.sdk.common.export.MemoryMode)
+No changes.
\ No newline at end of file
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValue.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValue.java
index 8dc9ed04b97..a509c48aef4 100644
--- a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValue.java
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValue.java
@@ -8,43 +8,97 @@
 import java.util.List;
 import java.util.Map;
 
+/**
+ * AnyValue mirrors the proto <a
+ * href="https://github.com/open-telemetry/opentelemetry-proto/blob/ac3242b03157295e4ee9e616af53b81517b06559/opentelemetry/proto/common/v1/common.proto#L28">AnyValue</a>
+ * message type, and is used to model any type.
+ *
+ * <p>It can be used to represent:
+ *
+ * <ul>
+ *   <li>Primitive values via {@link #of(long)}, {@link #of(String)}, {@link #of(boolean)}, {@link
+ *       #of(double)}.
+ *   <li>String-keyed maps (i.e. associative arrays, dictionaries) via {@link #of(KeyAnyValue...)},
+ *       {@link #of(Map)}. Note, because map values are type {@link AnyValue}, maps can be nested
+ *       within other maps.
+ *   <li>Arrays (heterogeneous or homogenous) via {@link #of(AnyValue[])}. Note, because array
+ *       values are type {@link AnyValue}, arrays can contain primitives, complex types like maps or
+ *       arrays, or any combination.
+ *   <li>Raw bytes via {@link #of(byte[])}
+ * </ul>
+ *
+ * @param <T> the type. See {@link #getValue()} for description of types.
+ */
 public interface AnyValue<T> {
 
+  /** Returns an {@link AnyValue} for the {@link String} value. */
   static AnyValue<String> of(String value) {
     return AnyValueString.create(value);
   }
 
+  /** Returns an {@link AnyValue} for the {@code boolean} value. */
   static AnyValue<Boolean> of(boolean value) {
     return AnyValueBoolean.create(value);
   }
 
+  /** Returns an {@link AnyValue} for the {@code long} value. */
   static AnyValue<Long> of(long value) {
     return AnyValueLong.create(value);
   }
 
+  /** Returns an {@link AnyValue} for the {@code double} value. */
   static AnyValue<Double> of(double value) {
     return AnyValueDouble.create(value);
   }
 
+  /** Returns an {@link AnyValue} for the {@code byte[]} value. */
   static AnyValue<byte[]> of(byte[] value) {
     return AnyValueBytes.create(value);
   }
 
+  /** Returns an {@link AnyValue} for the array of {@link AnyValue} values. */
   static AnyValue<List<AnyValue<?>>> of(AnyValue<?>... value) {
     return AnyValueArray.create(value);
   }
 
+  /**
+   * Returns an {@link AnyValue} for the array of {@link KeyAnyValue} values. {@link
+   * KeyAnyValue#getKey()} values should not repeat - duplicates may be dropped.
+   */
   static AnyValue<List<KeyAnyValue>> of(KeyAnyValue... value) {
     return KeyAnyValueList.create(value);
   }
 
+  /** Returns an {@link AnyValue} for the {@link Map} of key, {@link AnyValue}. */
   static AnyValue<List<KeyAnyValue>> of(Map<String, AnyValue<?>> value) {
     return KeyAnyValueList.createFromMap(value);
   }
 
+  /** Returns the type of this {@link AnyValue}. Useful for building switch statements. */
   AnyValueType getType();
 
+  /**
+   * Returns the value for this {@link AnyValue}.
+   *
+   * <p>The return type varies by {@link #getType()} as described below:
+   *
+   * <ul>
+   *   <li>{@link AnyValueType#STRING} returns {@link String}
+   *   <li>{@link AnyValueType#BOOLEAN} returns {@code boolean}
+   *   <li>{@link AnyValueType#LONG} returns {@code long}
+   *   <li>{@link AnyValueType#DOUBLE} returns {@code double}
+   *   <li>{@link AnyValueType#ARRAY} returns {@link List} of {@link AnyValue}
+   *   <li>{@link AnyValueType#KEY_VALUE_LIST} returns {@link List} of {@link KeyAnyValue}
+   *   <li>{@link AnyValueType#BYTES} returns {@code byte[]}
+   * </ul>
+   */
   T getValue();
 
+  /**
+   * Return a string encoding of this {@link AnyValue}. This is intended to be a fallback serialized
+   * representation in case there is no suitable encoding that can utilize {@link #getType()} /
+   * {@link #getValue()} to serialize specific types.
+   */
+  // TODO(jack-berg): Should this be a JSON encoding?
   String asString();
 }
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueArray.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueArray.java
index 551dbeb5405..dd96a60793d 100644
--- a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueArray.java
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueArray.java
@@ -22,7 +22,7 @@ private AnyValueArray(List<AnyValue<?>> value) {
   }
 
   static AnyValue<List<AnyValue<?>>> create(AnyValue<?>... value) {
-    Objects.requireNonNull(value, "value");
+    Objects.requireNonNull(value, "value must not be null");
     List<AnyValue<?>> list = new ArrayList<>(value.length);
     list.addAll(Arrays.asList(value));
     return new AnyValueArray(Collections.unmodifiableList(list));
@@ -47,4 +47,17 @@ public String asString() {
   public String toString() {
     return "AnyValueArray{" + asString() + "}";
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    return (o instanceof AnyValue) && Objects.equals(this.value, ((AnyValue<?>) o).getValue());
+  }
+
+  @Override
+  public int hashCode() {
+    return value.hashCode();
+  }
 }
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueBoolean.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueBoolean.java
index fe3717a064f..5fa862b777f 100644
--- a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueBoolean.java
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueBoolean.java
@@ -5,6 +5,8 @@
 
 package io.opentelemetry.extension.incubator.logs;
 
+import java.util.Objects;
+
 final class AnyValueBoolean implements AnyValue<Boolean> {
 
   private final boolean value;
@@ -36,4 +38,17 @@ public String asString() {
   public String toString() {
     return "AnyValueBoolean{" + asString() + "}";
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    return (o instanceof AnyValue) && Objects.equals(this.value, ((AnyValue<?>) o).getValue());
+  }
+
+  @Override
+  public int hashCode() {
+    return Boolean.hashCode(value);
+  }
 }
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueBytes.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueBytes.java
index d2cb9a84ed5..589cce83f9e 100644
--- a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueBytes.java
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueBytes.java
@@ -18,7 +18,7 @@ private AnyValueBytes(byte[] value) {
   }
 
   static AnyValue<byte[]> create(byte[] value) {
-    Objects.requireNonNull(value, "value");
+    Objects.requireNonNull(value, "value must not be null");
     return new AnyValueBytes(Arrays.copyOf(value, value.length));
   }
 
@@ -34,7 +34,8 @@ public byte[] getValue() {
 
   @Override
   public String asString() {
-    // TODO: base64 would be better, but isn't available in android and java
+    // TODO: base64 would be better, but isn't available in android and java. Can we vendor in a
+    // base64 implementation?
     char[] arr = new char[value.length * 2];
     OtelEncodingUtils.bytesToBase16(value, arr, value.length);
     return new String(arr);
@@ -44,4 +45,19 @@ public String asString() {
   public String toString() {
     return "AnyValueBytes{" + asString() + "}";
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    return (o instanceof AnyValue)
+        && ((AnyValue<?>) o).getType() == AnyValueType.BYTES
+        && Arrays.equals(this.value, (byte[]) ((AnyValue<?>) o).getValue());
+  }
+
+  @Override
+  public int hashCode() {
+    return Arrays.hashCode(value);
+  }
 }
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueDouble.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueDouble.java
index 143014f08b2..4e2cdccf33b 100644
--- a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueDouble.java
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueDouble.java
@@ -5,6 +5,8 @@
 
 package io.opentelemetry.extension.incubator.logs;
 
+import java.util.Objects;
+
 final class AnyValueDouble implements AnyValue<Double> {
 
   private final double value;
@@ -19,7 +21,7 @@ static AnyValue<Double> create(double value) {
 
   @Override
   public AnyValueType getType() {
-    return AnyValueType.LONG;
+    return AnyValueType.DOUBLE;
   }
 
   @Override
@@ -36,4 +38,17 @@ public String asString() {
   public String toString() {
     return "AnyValueDouble{" + asString() + "}";
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    return (o instanceof AnyValue) && Objects.equals(this.value, ((AnyValue<?>) o).getValue());
+  }
+
+  @Override
+  public int hashCode() {
+    return Double.hashCode(value);
+  }
 }
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueLong.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueLong.java
index c3c5d258ac3..558a08376ee 100644
--- a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueLong.java
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueLong.java
@@ -5,6 +5,8 @@
 
 package io.opentelemetry.extension.incubator.logs;
 
+import java.util.Objects;
+
 final class AnyValueLong implements AnyValue<Long> {
 
   private final long value;
@@ -36,4 +38,17 @@ public String asString() {
   public String toString() {
     return "AnyValueLong{" + asString() + "}";
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    return (o instanceof AnyValue) && Objects.equals(this.value, ((AnyValue<?>) o).getValue());
+  }
+
+  @Override
+  public int hashCode() {
+    return Long.hashCode(value);
+  }
 }
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueString.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueString.java
index 1980cec63a7..6a7b0a1c8e2 100644
--- a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueString.java
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueString.java
@@ -16,7 +16,7 @@ private AnyValueString(String value) {
   }
 
   static AnyValue<String> create(String value) {
-    Objects.requireNonNull(value, "value");
+    Objects.requireNonNull(value, "value must not be null");
     return new AnyValueString(value);
   }
 
@@ -39,4 +39,17 @@ public String asString() {
   public String toString() {
     return "AnyValueString{" + value + "}";
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    return (o instanceof AnyValue) && Objects.equals(this.value, ((AnyValue<?>) o).getValue());
+  }
+
+  @Override
+  public int hashCode() {
+    return value.hashCode();
+  }
 }
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueType.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueType.java
index 3f07849fc06..f683cc61ea5 100644
--- a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueType.java
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueType.java
@@ -5,6 +5,11 @@
 
 package io.opentelemetry.extension.incubator.logs;
 
+/**
+ * AnyValue type options, mirroring <a
+ * href="https://github.com/open-telemetry/opentelemetry-proto/blob/ac3242b03157295e4ee9e616af53b81517b06559/opentelemetry/proto/common/v1/common.proto#L31">AnyValue#value
+ * options</a>.
+ */
 public enum AnyValueType {
   STRING,
   BOOLEAN,
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/ExtendedLogRecordBuilder.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/ExtendedLogRecordBuilder.java
index 3503b1651df..b1ca789c1f6 100644
--- a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/ExtendedLogRecordBuilder.java
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/ExtendedLogRecordBuilder.java
@@ -7,7 +7,9 @@
 
 import io.opentelemetry.api.logs.LogRecordBuilder;
 
+/** Extended {@link LogRecordBuilder} with experimental APIs. */
 public interface ExtendedLogRecordBuilder extends LogRecordBuilder {
 
+  /** Set the body {@link AnyValue}. */
   LogRecordBuilder setBody(AnyValue<?> body);
 }
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/KeyAnyValue.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/KeyAnyValue.java
index 8b19b26867f..6aeb5eab6ab 100644
--- a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/KeyAnyValue.java
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/KeyAnyValue.java
@@ -5,13 +5,21 @@
 
 package io.opentelemetry.extension.incubator.logs;
 
+/**
+ * Key-value pair of {@link String} key and {@link AnyValue} value.
+ *
+ * @see AnyValue#of(KeyAnyValue...)
+ */
 public interface KeyAnyValue {
 
+  /** Returns a {@link KeyAnyValue} for the given {@code key} and {@code value}. */
   static KeyAnyValue of(String key, AnyValue<?> value) {
     return KeyAnyValueImpl.create(key, value);
   }
 
+  /** Returns the key. */
   String getKey();
 
+  /** Returns the value. */
   AnyValue<?> getAnyValue();
 }
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/KeyAnyValueList.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/KeyAnyValueList.java
index 28f850641e2..501a7a66ac0 100644
--- a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/KeyAnyValueList.java
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/KeyAnyValueList.java
@@ -23,14 +23,14 @@ private KeyAnyValueList(List<KeyAnyValue> value) {
   }
 
   static AnyValue<List<KeyAnyValue>> create(KeyAnyValue... value) {
-    Objects.requireNonNull(value, "value");
+    Objects.requireNonNull(value, "value must not be null");
     List<KeyAnyValue> list = new ArrayList<>(value.length);
     list.addAll(Arrays.asList(value));
     return new KeyAnyValueList(Collections.unmodifiableList(list));
   }
 
   static AnyValue<List<KeyAnyValue>> createFromMap(Map<String, AnyValue<?>> value) {
-    Objects.requireNonNull(value, "value");
+    Objects.requireNonNull(value, "value must not be null");
     KeyAnyValue[] array =
         value.entrySet().stream()
             .map(entry -> KeyAnyValue.of(entry.getKey(), entry.getValue()))
@@ -59,4 +59,17 @@ public String asString() {
   public String toString() {
     return "KeyAnyValueList{" + asString() + "}";
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    return (o instanceof AnyValue) && Objects.equals(this.value, ((AnyValue<?>) o).getValue());
+  }
+
+  @Override
+  public int hashCode() {
+    return value.hashCode();
+  }
 }
diff --git a/extensions/incubator/src/test/java/io/opentelemetry/extension/incubator/logs/AnyValueTest.java b/extensions/incubator/src/test/java/io/opentelemetry/extension/incubator/logs/AnyValueTest.java
index 1c8bf63a575..47b2d1c4d58 100644
--- a/extensions/incubator/src/test/java/io/opentelemetry/extension/incubator/logs/AnyValueTest.java
+++ b/extensions/incubator/src/test/java/io/opentelemetry/extension/incubator/logs/AnyValueTest.java
@@ -6,12 +6,15 @@
 package io.opentelemetry.extension.incubator.logs;
 
 import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.junit.jupiter.params.provider.Arguments.arguments;
 
 import io.opentelemetry.api.internal.OtelEncodingUtils;
 import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedHashMap;
+import java.util.Map;
 import java.util.stream.Stream;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
@@ -20,6 +23,142 @@
 
 class AnyValueTest {
 
+  @Test
+  void anyValue_OfString() {
+    assertThat(AnyValue.of("foo"))
+        .satisfies(
+            anyValue -> {
+              assertThat(anyValue.getType()).isEqualTo(AnyValueType.STRING);
+              assertThat(anyValue.getValue()).isEqualTo("foo");
+              assertThat(anyValue).hasSameHashCodeAs(AnyValue.of("foo"));
+            });
+  }
+
+  @Test
+  void anyValue_OfBoolean() {
+    assertThat(AnyValue.of(true))
+        .satisfies(
+            anyValue -> {
+              assertThat(anyValue.getType()).isEqualTo(AnyValueType.BOOLEAN);
+              assertThat(anyValue.getValue()).isEqualTo(true);
+              assertThat(anyValue).hasSameHashCodeAs(AnyValue.of(true));
+            });
+  }
+
+  @Test
+  void anyValue_OfLong() {
+    assertThat(AnyValue.of(1L))
+        .satisfies(
+            anyValue -> {
+              assertThat(anyValue.getType()).isEqualTo(AnyValueType.LONG);
+              assertThat(anyValue.getValue()).isEqualTo(1L);
+              assertThat(anyValue).hasSameHashCodeAs(AnyValue.of(1L));
+            });
+  }
+
+  @Test
+  void anyValue_OfDouble() {
+    assertThat(AnyValue.of(1.1))
+        .satisfies(
+            anyValue -> {
+              assertThat(anyValue.getType()).isEqualTo(AnyValueType.DOUBLE);
+              assertThat(anyValue.getValue()).isEqualTo(1.1);
+              assertThat(anyValue).hasSameHashCodeAs(AnyValue.of(1.1));
+            });
+  }
+
+  @Test
+  void anyValue_OfByteArray() {
+    assertThat(AnyValue.of(new byte[] {'a', 'b'}))
+        .satisfies(
+            anyValue -> {
+              assertThat(anyValue.getType()).isEqualTo(AnyValueType.BYTES);
+              assertThat(anyValue.getValue()).isEqualTo(new byte[] {'a', 'b'});
+              assertThat(anyValue).hasSameHashCodeAs(AnyValue.of(new byte[] {'a', 'b'}));
+            });
+  }
+
+  @Test
+  void anyValue_OfAnyValueArray() {
+    assertThat(AnyValue.of(AnyValue.of(true), AnyValue.of(1L)))
+        .satisfies(
+            anyValue -> {
+              assertThat(anyValue.getType()).isEqualTo(AnyValueType.ARRAY);
+              assertThat(anyValue.getValue())
+                  .isEqualTo(Arrays.asList(AnyValue.of(true), AnyValue.of(1L)));
+              assertThat(anyValue)
+                  .hasSameHashCodeAs(AnyValue.of(AnyValue.of(true), AnyValue.of(1L)));
+            });
+  }
+
+  @Test
+  @SuppressWarnings("DoubleBraceInitialization")
+  void anyValue_OfKeyValueList() {
+    assertThat(
+            AnyValue.of(
+                KeyAnyValue.of("bool", AnyValue.of(true)), KeyAnyValue.of("long", AnyValue.of(1L))))
+        .satisfies(
+            anyValue -> {
+              assertThat(anyValue.getType()).isEqualTo(AnyValueType.KEY_VALUE_LIST);
+              assertThat(anyValue.getValue())
+                  .isEqualTo(
+                      Arrays.asList(
+                          KeyAnyValue.of("bool", AnyValue.of(true)),
+                          KeyAnyValue.of("long", AnyValue.of(1L))));
+              assertThat(anyValue)
+                  .hasSameHashCodeAs(
+                      AnyValue.of(
+                          KeyAnyValue.of("bool", AnyValue.of(true)),
+                          KeyAnyValue.of("long", AnyValue.of(1L))));
+            });
+
+    assertThat(
+            AnyValue.of(
+                new LinkedHashMap<String, AnyValue<?>>() {
+                  {
+                    put("bool", AnyValue.of(true));
+                    put("long", AnyValue.of(1L));
+                  }
+                }))
+        .satisfies(
+            anyValue -> {
+              assertThat(anyValue.getType()).isEqualTo(AnyValueType.KEY_VALUE_LIST);
+              assertThat(anyValue.getValue())
+                  .isEqualTo(
+                      Arrays.asList(
+                          KeyAnyValue.of("bool", AnyValue.of(true)),
+                          KeyAnyValue.of("long", AnyValue.of(1L))));
+              assertThat(anyValue)
+                  .hasSameHashCodeAs(
+                      AnyValue.of(
+                          new LinkedHashMap<String, AnyValue<?>>() {
+                            {
+                              put("bool", AnyValue.of(true));
+                              put("long", AnyValue.of(1L));
+                            }
+                          }));
+            });
+  }
+
+  @Test
+  void anyValue_NullsNotAllowed() {
+    assertThatThrownBy(() -> AnyValue.of((String) null))
+        .isInstanceOf(NullPointerException.class)
+        .hasMessageContaining("value must not be null");
+    assertThatThrownBy(() -> AnyValue.of((byte[]) null))
+        .isInstanceOf(NullPointerException.class)
+        .hasMessageContaining("value must not be null");
+    assertThatThrownBy(() -> AnyValue.of((AnyValue<?>[]) null))
+        .isInstanceOf(NullPointerException.class)
+        .hasMessageContaining("value must not be null");
+    assertThatThrownBy(() -> AnyValue.of((KeyAnyValue[]) null))
+        .isInstanceOf(NullPointerException.class)
+        .hasMessageContaining("value must not be null");
+    assertThatThrownBy(() -> AnyValue.of((Map<String, AnyValue<?>>) null))
+        .isInstanceOf(NullPointerException.class)
+        .hasMessageContaining("value must not be null");
+  }
+
   @ParameterizedTest
   @MethodSource("asStringArgs")
   void asString(AnyValue<?> value, String expectedAsString) {
@@ -74,6 +213,4 @@ void anyValueByteAsString() {
     byte[] decodedBytes = OtelEncodingUtils.bytesFromBase16(base16Encoded, base16Encoded.length());
     assertThat(new String(decodedBytes, StandardCharsets.UTF_8)).isEqualTo(str);
   }
-
-  // TODO: test equals, hashcode, getType
 }
diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/AnyValueBody.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/AnyValueBody.java
index 4432c392a4d..f756b8ac10a 100644
--- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/AnyValueBody.java
+++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/AnyValueBody.java
@@ -35,4 +35,9 @@ public String asString() {
   public AnyValue<?> getAnyValue() {
     return value;
   }
+
+  @Override
+  public String toString() {
+    return "AnyValueBody{" + asString() + "}";
+  }
 }
diff --git a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/AnyValueBodyTest.java b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/AnyValueBodyTest.java
index 09486fdaa06..be02c79f656 100644
--- a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/AnyValueBodyTest.java
+++ b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/AnyValueBodyTest.java
@@ -13,6 +13,7 @@
 import io.opentelemetry.extension.incubator.logs.KeyAnyValue;
 import io.opentelemetry.sdk.logs.data.Body;
 import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor;
+import io.opentelemetry.sdk.logs.internal.AnyValueBody;
 import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter;
 import java.nio.charset.StandardCharsets;
 import java.util.LinkedHashMap;
@@ -38,6 +39,8 @@ void anyValueBody() {
             logRecordData -> {
               assertThat(logRecordData.getBody().getType()).isEqualTo(Body.Type.ANY_VALUE);
               assertThat(logRecordData.getBody().asString()).isEqualTo("1");
+              assertThat(((AnyValueBody) logRecordData.getBody()).getAnyValue())
+                  .isEqualTo(AnyValue.of(1));
             });
     exporter.reset();
 
@@ -51,6 +54,8 @@ void anyValueBody() {
             logRecordData -> {
               assertThat(logRecordData.getBody().getType()).isEqualTo(Body.Type.ANY_VALUE);
               assertThat(logRecordData.getBody().asString()).isEqualTo("68656c6c6f20776f726c64");
+              assertThat(((AnyValueBody) logRecordData.getBody()).getAnyValue())
+                  .isEqualTo(AnyValue.of("hello world".getBytes(StandardCharsets.UTF_8)));
             });
     exporter.reset();
 
@@ -101,6 +106,27 @@ void anyValueBody() {
                           + "arr_key=[entry1, 2, 3.3], "
                           + "key_value_list_key=[child_str_key1=child_value1, child_str_key2=child_value2]"
                           + "]");
+              assertThat(((AnyValueBody) logRecordData.getBody()).getAnyValue())
+                  .isEqualTo(
+                      AnyValue.of(
+                          KeyAnyValue.of("str_key", AnyValue.of("value")),
+                          KeyAnyValue.of("bool_key", AnyValue.of(true)),
+                          KeyAnyValue.of("long_key", AnyValue.of(1L)),
+                          KeyAnyValue.of("double_key", AnyValue.of(1.1)),
+                          KeyAnyValue.of(
+                              "bytes_key", AnyValue.of("bytes".getBytes(StandardCharsets.UTF_8))),
+                          KeyAnyValue.of(
+                              "arr_key",
+                              AnyValue.of(AnyValue.of("entry1"), AnyValue.of(2), AnyValue.of(3.3))),
+                          KeyAnyValue.of(
+                              "key_value_list_key",
+                              AnyValue.of(
+                                  new LinkedHashMap<String, AnyValue<?>>() {
+                                    {
+                                      put("child_str_key1", AnyValue.of("child_value1"));
+                                      put("child_str_key2", AnyValue.of("child_value2"));
+                                    }
+                                  }))));
             });
     exporter.reset();
 
@@ -114,6 +140,9 @@ void anyValueBody() {
             logRecordData -> {
               assertThat(logRecordData.getBody().getType()).isEqualTo(Body.Type.ANY_VALUE);
               assertThat(logRecordData.getBody().asString()).isEqualTo("[entry1, entry2, 3]");
+              assertThat(((AnyValueBody) logRecordData.getBody()).getAnyValue())
+                  .isEqualTo(
+                      AnyValue.of(AnyValue.of("entry1"), AnyValue.of("entry2"), AnyValue.of(3)));
             });
     exporter.reset();
   }

From 943c3fadcc5699acda02f2a2c4f4c8cbfe2d07e1 Mon Sep 17 00:00:00 2001
From: Jack Berg <jberg@newrelic.com>
Date: Fri, 13 Oct 2023 15:56:40 -0500
Subject: [PATCH 4/6] japicmp

---
 docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt
index 3013553d6ea..fac1cdbcecc 100644
--- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt
+++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt
@@ -1,2 +1,4 @@
-Comparing source compatibility of  against
-No changes.
+Comparing source compatibility of  against 
+***  MODIFIED ENUM: PUBLIC STATIC FINAL io.opentelemetry.sdk.logs.data.Body$Type  (compatible)
+	===  CLASS FILE FORMAT VERSION: 52.0 <- 52.0
+	+++  NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.sdk.logs.data.Body$Type ANY_VALUE

From 07a51c23c16e6b86bbe834678f40fc34ca28432e Mon Sep 17 00:00:00 2001
From: Jack Berg <jberg@newrelic.com>
Date: Wed, 18 Oct 2023 09:46:08 -0500
Subject: [PATCH 5/6] PR feedback

---
 .../kotlin/otel.java-conventions.gradle.kts   |  5 +++-
 .../otlp/logs/LogsRequestMarshalerTest.java   | 15 +-----------
 .../extension/incubator/logs/AnyValue.java    |  6 +++--
 .../incubator/logs/AnyValueBytes.java         | 23 +++++++++----------
 .../incubator/logs/KeyAnyValueList.java       |  2 +-
 .../incubator/logs/AnyValueTest.java          |  9 +++++++-
 6 files changed, 29 insertions(+), 31 deletions(-)

diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts
index 4d601c406dc..43791644017 100644
--- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts
+++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts
@@ -74,7 +74,10 @@ tasks {
             // https://groups.google.com/forum/#!topic/bazel-discuss/_R3A9TJSoPM
             "-Xlint:-processing",
             // We suppress the "options" warning because it prevents compilation on modern JDKs
-            "-Xlint:-options"
+            "-Xlint:-options",
+
+            // Fail build on any warning
+            "-Werror",
           ),
         )
       }
diff --git a/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/logs/LogsRequestMarshalerTest.java b/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/logs/LogsRequestMarshalerTest.java
index 59b60fe2ac7..306cfe1ea14 100644
--- a/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/logs/LogsRequestMarshalerTest.java
+++ b/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/logs/LogsRequestMarshalerTest.java
@@ -24,7 +24,6 @@
 import io.opentelemetry.proto.common.v1.AnyValue;
 import io.opentelemetry.proto.common.v1.InstrumentationScope;
 import io.opentelemetry.proto.common.v1.KeyValue;
-import io.opentelemetry.proto.common.v1.KeyValueList;
 import io.opentelemetry.proto.logs.v1.LogRecord;
 import io.opentelemetry.proto.logs.v1.ResourceLogs;
 import io.opentelemetry.proto.logs.v1.ScopeLogs;
@@ -154,19 +153,7 @@ void toProtoLogRecord_MinimalFields() {
     assertThat(logRecord.getSeverityText()).isBlank();
     assertThat(logRecord.getSeverityNumber().getNumber())
         .isEqualTo(Severity.UNDEFINED_SEVERITY_NUMBER.getSeverityNumber());
-    assertThat(logRecord.getBody())
-        .isEqualTo(
-            AnyValue.newBuilder()
-                .setKvlistValue(
-                    KeyValueList.newBuilder()
-                        .addValues(
-                            KeyValue.newBuilder()
-                                .setKey("key")
-                                .setValue(AnyValue.newBuilder().setStringValue("foo").build())
-                                .build())
-                        .build())
-                .setStringValue("")
-                .build());
+    assertThat(logRecord.getBody()).isEqualTo(AnyValue.newBuilder().setStringValue("").build());
     assertThat(logRecord.getAttributesList()).isEmpty();
     assertThat(logRecord.getDroppedAttributesCount()).isZero();
     assertThat(logRecord.getTimeUnixNano()).isEqualTo(12345);
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValue.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValue.java
index a509c48aef4..6f250e10da7 100644
--- a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValue.java
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValue.java
@@ -5,6 +5,7 @@
 
 package io.opentelemetry.extension.incubator.logs;
 
+import java.nio.ByteBuffer;
 import java.util.List;
 import java.util.Map;
 
@@ -52,7 +53,7 @@ static AnyValue<Double> of(double value) {
   }
 
   /** Returns an {@link AnyValue} for the {@code byte[]} value. */
-  static AnyValue<byte[]> of(byte[] value) {
+  static AnyValue<ByteBuffer> of(byte[] value) {
     return AnyValueBytes.create(value);
   }
 
@@ -89,7 +90,8 @@ static AnyValue<List<KeyAnyValue>> of(Map<String, AnyValue<?>> value) {
    *   <li>{@link AnyValueType#DOUBLE} returns {@code double}
    *   <li>{@link AnyValueType#ARRAY} returns {@link List} of {@link AnyValue}
    *   <li>{@link AnyValueType#KEY_VALUE_LIST} returns {@link List} of {@link KeyAnyValue}
-   *   <li>{@link AnyValueType#BYTES} returns {@code byte[]}
+   *   <li>{@link AnyValueType#BYTES} returns read only {@link ByteBuffer}. See {@link
+   *       ByteBuffer#asReadOnlyBuffer()}.
    * </ul>
    */
   T getValue();
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueBytes.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueBytes.java
index 589cce83f9e..1677a3313e8 100644
--- a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueBytes.java
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/AnyValueBytes.java
@@ -6,18 +6,19 @@
 package io.opentelemetry.extension.incubator.logs;
 
 import io.opentelemetry.api.internal.OtelEncodingUtils;
+import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.Objects;
 
-final class AnyValueBytes implements AnyValue<byte[]> {
+final class AnyValueBytes implements AnyValue<ByteBuffer> {
 
-  private final byte[] value;
+  private final byte[] raw;
 
   private AnyValueBytes(byte[] value) {
-    this.value = value;
+    this.raw = value;
   }
 
-  static AnyValue<byte[]> create(byte[] value) {
+  static AnyValue<ByteBuffer> create(byte[] value) {
     Objects.requireNonNull(value, "value must not be null");
     return new AnyValueBytes(Arrays.copyOf(value, value.length));
   }
@@ -28,16 +29,16 @@ public AnyValueType getType() {
   }
 
   @Override
-  public byte[] getValue() {
-    return value;
+  public ByteBuffer getValue() {
+    return ByteBuffer.wrap(raw).asReadOnlyBuffer();
   }
 
   @Override
   public String asString() {
     // TODO: base64 would be better, but isn't available in android and java. Can we vendor in a
     // base64 implementation?
-    char[] arr = new char[value.length * 2];
-    OtelEncodingUtils.bytesToBase16(value, arr, value.length);
+    char[] arr = new char[raw.length * 2];
+    OtelEncodingUtils.bytesToBase16(raw, arr, raw.length);
     return new String(arr);
   }
 
@@ -51,13 +52,11 @@ public boolean equals(Object o) {
     if (this == o) {
       return true;
     }
-    return (o instanceof AnyValue)
-        && ((AnyValue<?>) o).getType() == AnyValueType.BYTES
-        && Arrays.equals(this.value, (byte[]) ((AnyValue<?>) o).getValue());
+    return (o instanceof AnyValueBytes) && Arrays.equals(this.raw, ((AnyValueBytes) o).raw);
   }
 
   @Override
   public int hashCode() {
-    return Arrays.hashCode(value);
+    return Arrays.hashCode(raw);
   }
 }
diff --git a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/KeyAnyValueList.java b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/KeyAnyValueList.java
index 501a7a66ac0..427ed4cb13a 100644
--- a/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/KeyAnyValueList.java
+++ b/extensions/incubator/src/main/java/io/opentelemetry/extension/incubator/logs/KeyAnyValueList.java
@@ -51,7 +51,7 @@ public List<KeyAnyValue> getValue() {
   @Override
   public String asString() {
     return value.stream()
-        .map(entry -> entry.getKey() + "=" + entry.getAnyValue().asString())
+        .map(item -> item.getKey() + "=" + item.getAnyValue().asString())
         .collect(joining(", ", "[", "]"));
   }
 
diff --git a/extensions/incubator/src/test/java/io/opentelemetry/extension/incubator/logs/AnyValueTest.java b/extensions/incubator/src/test/java/io/opentelemetry/extension/incubator/logs/AnyValueTest.java
index 47b2d1c4d58..842cd505c61 100644
--- a/extensions/incubator/src/test/java/io/opentelemetry/extension/incubator/logs/AnyValueTest.java
+++ b/extensions/incubator/src/test/java/io/opentelemetry/extension/incubator/logs/AnyValueTest.java
@@ -10,6 +10,8 @@
 import static org.junit.jupiter.params.provider.Arguments.arguments;
 
 import io.opentelemetry.api.internal.OtelEncodingUtils;
+import java.nio.ByteBuffer;
+import java.nio.ReadOnlyBufferException;
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.Collections;
@@ -73,7 +75,12 @@ void anyValue_OfByteArray() {
         .satisfies(
             anyValue -> {
               assertThat(anyValue.getType()).isEqualTo(AnyValueType.BYTES);
-              assertThat(anyValue.getValue()).isEqualTo(new byte[] {'a', 'b'});
+              ByteBuffer value = anyValue.getValue();
+              // AnyValueBytes returns read only view of ByteBuffer
+              assertThatThrownBy(value::array).isInstanceOf(ReadOnlyBufferException.class);
+              byte[] bytes = new byte[value.remaining()];
+              value.get(bytes);
+              assertThat(bytes).isEqualTo(new byte[] {'a', 'b'});
               assertThat(anyValue).hasSameHashCodeAs(AnyValue.of(new byte[] {'a', 'b'}));
             });
   }

From 72daf329df67400eb4ef14b7cf1f8474b1894d68 Mon Sep 17 00:00:00 2001
From: Jack Berg <jberg@newrelic.com>
Date: Tue, 31 Oct 2023 13:28:42 -0500
Subject: [PATCH 6/6] Remove Body#ANY_VALUE

---
 .../opentelemetry-sdk-logs.txt                |  4 +---
 .../io/opentelemetry/sdk/logs/data/Body.java  |  5 +++--
 .../sdk/logs/internal/AnyValueBody.java       |  4 ++--
 .../sdk/logs/AnyValueBodyTest.java            | 21 +++++++++++--------
 4 files changed, 18 insertions(+), 16 deletions(-)

diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt
index fac1cdbcecc..df26146497b 100644
--- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt
+++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt
@@ -1,4 +1,2 @@
 Comparing source compatibility of  against 
-***  MODIFIED ENUM: PUBLIC STATIC FINAL io.opentelemetry.sdk.logs.data.Body$Type  (compatible)
-	===  CLASS FILE FORMAT VERSION: 52.0 <- 52.0
-	+++  NEW FIELD: PUBLIC(+) STATIC(+) FINAL(+) io.opentelemetry.sdk.logs.data.Body$Type ANY_VALUE
+No changes.
\ No newline at end of file
diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/data/Body.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/data/Body.java
index 3258ae9c4f0..2dc7957de91 100644
--- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/data/Body.java
+++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/data/Body.java
@@ -21,8 +21,9 @@ public interface Body {
   /** An enum that represents all the possible value types for an {@code Body}. */
   enum Type {
     EMPTY,
-    STRING,
-    ANY_VALUE
+    STRING
+    // TODO (jack-berg): Add ANY_VALUE type when API for setting body to AnyValue is stable
+    // ANY_VALUE
   }
 
   /**
diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/AnyValueBody.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/AnyValueBody.java
index f756b8ac10a..7a1a9f2138f 100644
--- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/AnyValueBody.java
+++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/AnyValueBody.java
@@ -24,7 +24,7 @@ public static Body create(AnyValue<?> value) {
 
   @Override
   public Type getType() {
-    return Type.ANY_VALUE;
+    return Type.STRING;
   }
 
   @Override
@@ -32,7 +32,7 @@ public String asString() {
     return value.asString();
   }
 
-  public AnyValue<?> getAnyValue() {
+  public AnyValue<?> asAnyValue() {
     return value;
   }
 
diff --git a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/AnyValueBodyTest.java b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/AnyValueBodyTest.java
index be02c79f656..e793dc513d0 100644
--- a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/AnyValueBodyTest.java
+++ b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/AnyValueBodyTest.java
@@ -11,7 +11,6 @@
 import io.opentelemetry.extension.incubator.logs.AnyValue;
 import io.opentelemetry.extension.incubator.logs.ExtendedLogRecordBuilder;
 import io.opentelemetry.extension.incubator.logs.KeyAnyValue;
-import io.opentelemetry.sdk.logs.data.Body;
 import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor;
 import io.opentelemetry.sdk.logs.internal.AnyValueBody;
 import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter;
@@ -37,9 +36,10 @@ void anyValueBody() {
         .hasSize(1)
         .satisfiesExactly(
             logRecordData -> {
-              assertThat(logRecordData.getBody().getType()).isEqualTo(Body.Type.ANY_VALUE);
+              // TODO (jack-berg): add assertion when ANY_VALUE is added to Body.Type
+              // assertThat(logRecordData.getBody().getType()).isEqualTo(Body.Type.ANY_VALUE);
               assertThat(logRecordData.getBody().asString()).isEqualTo("1");
-              assertThat(((AnyValueBody) logRecordData.getBody()).getAnyValue())
+              assertThat(((AnyValueBody) logRecordData.getBody()).asAnyValue())
                   .isEqualTo(AnyValue.of(1));
             });
     exporter.reset();
@@ -52,9 +52,10 @@ void anyValueBody() {
         .hasSize(1)
         .satisfiesExactly(
             logRecordData -> {
-              assertThat(logRecordData.getBody().getType()).isEqualTo(Body.Type.ANY_VALUE);
+              // TODO (jack-berg): add assertion when ANY_VALUE is added to Body.Type
+              // assertThat(logRecordData.getBody().getType()).isEqualTo(Body.Type.ANY_VALUE);
               assertThat(logRecordData.getBody().asString()).isEqualTo("68656c6c6f20776f726c64");
-              assertThat(((AnyValueBody) logRecordData.getBody()).getAnyValue())
+              assertThat(((AnyValueBody) logRecordData.getBody()).asAnyValue())
                   .isEqualTo(AnyValue.of("hello world".getBytes(StandardCharsets.UTF_8)));
             });
     exporter.reset();
@@ -94,7 +95,8 @@ void anyValueBody() {
         .hasSize(1)
         .satisfiesExactly(
             logRecordData -> {
-              assertThat(logRecordData.getBody().getType()).isEqualTo(Body.Type.ANY_VALUE);
+              // TODO (jack-berg): add assertion when ANY_VALUE is added to Body.Type
+              // assertThat(logRecordData.getBody().getType()).isEqualTo(Body.Type.ANY_VALUE);
               assertThat(logRecordData.getBody().asString())
                   .isEqualTo(
                       "["
@@ -106,7 +108,7 @@ void anyValueBody() {
                           + "arr_key=[entry1, 2, 3.3], "
                           + "key_value_list_key=[child_str_key1=child_value1, child_str_key2=child_value2]"
                           + "]");
-              assertThat(((AnyValueBody) logRecordData.getBody()).getAnyValue())
+              assertThat(((AnyValueBody) logRecordData.getBody()).asAnyValue())
                   .isEqualTo(
                       AnyValue.of(
                           KeyAnyValue.of("str_key", AnyValue.of("value")),
@@ -138,9 +140,10 @@ void anyValueBody() {
         .hasSize(1)
         .satisfiesExactly(
             logRecordData -> {
-              assertThat(logRecordData.getBody().getType()).isEqualTo(Body.Type.ANY_VALUE);
+              // TODO (jack-berg): add assertion when ANY_VALUE is added to Body.Type
+              // assertThat(logRecordData.getBody().getType()).isEqualTo(Body.Type.ANY_VALUE);
               assertThat(logRecordData.getBody().asString()).isEqualTo("[entry1, entry2, 3]");
-              assertThat(((AnyValueBody) logRecordData.getBody()).getAnyValue())
+              assertThat(((AnyValueBody) logRecordData.getBody()).asAnyValue())
                   .isEqualTo(
                       AnyValue.of(AnyValue.of("entry1"), AnyValue.of("entry2"), AnyValue.of(3)));
             });