Experimental support for Log AnyValue body (#5880)
This commit is contained in:
parent
ccb2e04237
commit
efa46a5dcc
|
|
@ -41,7 +41,7 @@ final class LogMarshaler extends MarshalerWithSize {
|
|||
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()));
|
||||
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.extension.incubator.logs;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
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<ByteBuffer> 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 read only {@link ByteBuffer}. See {@link
|
||||
* ByteBuffer#asReadOnlyBuffer()}.
|
||||
* </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();
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 must not be null");
|
||||
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() + "}";
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.extension.incubator.logs;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
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() + "}";
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
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<ByteBuffer> {
|
||||
|
||||
private final byte[] raw;
|
||||
|
||||
private AnyValueBytes(byte[] value) {
|
||||
this.raw = value;
|
||||
}
|
||||
|
||||
static AnyValue<ByteBuffer> create(byte[] value) {
|
||||
Objects.requireNonNull(value, "value must not be null");
|
||||
return new AnyValueBytes(Arrays.copyOf(value, value.length));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnyValueType getType() {
|
||||
return AnyValueType.BYTES;
|
||||
}
|
||||
|
||||
@Override
|
||||
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[raw.length * 2];
|
||||
OtelEncodingUtils.bytesToBase16(raw, arr, raw.length);
|
||||
return new String(arr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AnyValueBytes{" + asString() + "}";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
return (o instanceof AnyValueBytes) && Arrays.equals(this.raw, ((AnyValueBytes) o).raw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(raw);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.extension.incubator.logs;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
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.DOUBLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String asString() {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.extension.incubator.logs;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
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() + "}";
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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 must not be null");
|
||||
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 + "}";
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
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,
|
||||
LONG,
|
||||
DOUBLE,
|
||||
ARRAY,
|
||||
KEY_VALUE_LIST,
|
||||
BYTES
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.extension.incubator.logs;
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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 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 must not be null");
|
||||
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(item -> item.getKey() + "=" + item.getAnyValue().asString())
|
||||
.collect(joining(", ", "[", "]"));
|
||||
}
|
||||
|
||||
@Override
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* 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.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
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;
|
||||
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;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
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);
|
||||
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'}));
|
||||
});
|
||||
}
|
||||
|
||||
@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) {
|
||||
assertThat(value.asString()).isEqualTo(expectedAsString);
|
||||
}
|
||||
|
||||
@SuppressWarnings("DoubleBraceInitialization")
|
||||
private static Stream<Arguments> asStringArgs() {
|
||||
return Stream.of(
|
||||
// primitives
|
||||
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.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.of(
|
||||
KeyAnyValue.of("key1", AnyValue.of("val1")),
|
||||
KeyAnyValue.of("key2", AnyValue.of(2))),
|
||||
"[key1=val1, key2=2]"),
|
||||
// key value list from map
|
||||
arguments(
|
||||
AnyValue.of(
|
||||
new LinkedHashMap<String, AnyValue<?>>() {
|
||||
{
|
||||
put("key1", AnyValue.of("val1"));
|
||||
put("key2", AnyValue.of(2));
|
||||
}
|
||||
}),
|
||||
"[key1=val1, key2=2]"),
|
||||
// map of map
|
||||
arguments(
|
||||
AnyValue.of(
|
||||
Collections.singletonMap(
|
||||
"child",
|
||||
AnyValue.of(Collections.singletonMap("grandchild", AnyValue.of("str"))))),
|
||||
"[child=[grandchild=str]]"),
|
||||
// bytes
|
||||
arguments(
|
||||
AnyValue.of("hello world".getBytes(StandardCharsets.UTF_8)), "68656c6c6f20776f726c64"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void anyValueByteAsString() {
|
||||
// TODO: add more test cases
|
||||
String str = "hello world";
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,15 +10,18 @@ import io.opentelemetry.api.logs.LogRecordBuilder;
|
|||
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 @@ final class SdkLogRecordBuilder implements LogRecordBuilder {
|
|||
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) {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ public interface Body {
|
|||
enum Type {
|
||||
EMPTY,
|
||||
STRING
|
||||
// TODO (jack-berg): Add ANY_VALUE type when API for setting body to AnyValue is stable
|
||||
// ANY_VALUE
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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.STRING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String asString() {
|
||||
return value.asString();
|
||||
}
|
||||
|
||||
public AnyValue<?> asAnyValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AnyValueBody{" + asString() + "}";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* 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.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;
|
||||
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.of(1)).emit();
|
||||
assertThat(exporter.getFinishedLogRecordItems())
|
||||
.hasSize(1)
|
||||
.satisfiesExactly(
|
||||
logRecordData -> {
|
||||
// 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()).asAnyValue())
|
||||
.isEqualTo(AnyValue.of(1));
|
||||
});
|
||||
exporter.reset();
|
||||
|
||||
// ...or a byte array of raw data
|
||||
extendedLogRecordBuilder(logger)
|
||||
.setBody(AnyValue.of("hello world".getBytes(StandardCharsets.UTF_8)))
|
||||
.emit();
|
||||
assertThat(exporter.getFinishedLogRecordItems())
|
||||
.hasSize(1)
|
||||
.satisfiesExactly(
|
||||
logRecordData -> {
|
||||
// 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()).asAnyValue())
|
||||
.isEqualTo(AnyValue.of("hello world".getBytes(StandardCharsets.UTF_8)));
|
||||
});
|
||||
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.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"));
|
||||
}
|
||||
}))))
|
||||
.emit();
|
||||
assertThat(exporter.getFinishedLogRecordItems())
|
||||
.hasSize(1)
|
||||
.satisfiesExactly(
|
||||
logRecordData -> {
|
||||
// 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(
|
||||
"["
|
||||
+ "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]"
|
||||
+ "]");
|
||||
assertThat(((AnyValueBody) logRecordData.getBody()).asAnyValue())
|
||||
.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();
|
||||
|
||||
// ..or an array (optionally with heterogeneous types)
|
||||
extendedLogRecordBuilder(logger)
|
||||
.setBody(AnyValue.of(AnyValue.of("entry1"), AnyValue.of("entry2"), AnyValue.of(3)))
|
||||
.emit();
|
||||
assertThat(exporter.getFinishedLogRecordItems())
|
||||
.hasSize(1)
|
||||
.satisfiesExactly(
|
||||
logRecordData -> {
|
||||
// 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()).asAnyValue())
|
||||
.isEqualTo(
|
||||
AnyValue.of(AnyValue.of("entry1"), AnyValue.of("entry2"), AnyValue.of(3)));
|
||||
});
|
||||
exporter.reset();
|
||||
}
|
||||
|
||||
ExtendedLogRecordBuilder extendedLogRecordBuilder(Logger logger) {
|
||||
return (ExtendedLogRecordBuilder) logger.logRecordBuilder();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue