Skip to content

Commit

Permalink
Experimental support for Log AnyValue body (#5880)
Browse files Browse the repository at this point in the history
  • Loading branch information
jack-berg authored Oct 31, 2023
1 parent ccb2e04 commit efa46a5
Show file tree
Hide file tree
Showing 20 changed files with 1,041 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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()));

Expand Down
2 changes: 2 additions & 0 deletions extensions/incubator/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
}
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading

0 comments on commit efa46a5

Please sign in to comment.