Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stabilize log any value #6591

Merged
merged 8 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.api.incubator.logs;
package io.opentelemetry.api.common;

import java.nio.ByteBuffer;
import java.util.List;
Expand All @@ -28,6 +28,9 @@
* <li>Raw bytes via {@link #of(byte[])}
* </ul>
*
* <p>Currently, AnyValue is only used as an argument for {@link
* io.opentelemetry.api.logs.LogRecordBuilder#setBody(AnyValue)}.
*
* @param <T> the type. See {@link #getValue()} for description of types.
*/
public interface AnyValue<T> {
Expand Down Expand Up @@ -105,6 +108,9 @@ static AnyValue<List<KeyAnyValue>> of(Map<String, AnyValue<?>> value) {
* 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.
*
* <p>WARNING: No guarantees are made about the encoding of this string response. It MAY change in
* a future minor release. If you need a reliable string encoding, write your own serializer.
*/
// TODO(jack-berg): Should this be a JSON encoding?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there anything in spec about this? it seems like a lot of backends will get this format by default given the fallback implementation of getBody() and so it would be great if this format could be stable-ish

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The closest we have is https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/common/attribute-type-mapping.md, which explains how to map data structures to AnyValue, but nothing about a string representation of AnyValue. If the spec did have something, I imagine it might either draw inspiration from or be defined as the standard protobuf JSON mapping.

I agree that a stable spec would be ideal. But I also think we can proceed without it. Ideally, exporters quickly update to be able to properly serialize AnyValue bodies. The goal of asString() is just to be a fallback allowing the information to be transmitted, but in a format which implies that you shouldn't depend on it. Seeing data in this format is a signal that something is suboptimal in the export pipeline, while still allowing things to work.

If you look at a test of AnyValue#asString, you see a format which is JSON-like, but definitely not actually JSON. The format is probably best described as an idiomatic implementation of Java toString(). It could easily be adapted to be actual valid JSON, but that might reduce the incentive for exporters to properly serialize.

String asString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.api.incubator.logs;
package io.opentelemetry.api.common;

import static java.util.stream.Collectors.joining;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.api.incubator.logs;
package io.opentelemetry.api.common;

import java.util.Objects;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.api.incubator.logs;
package io.opentelemetry.api.common;

import java.nio.ByteBuffer;
import java.util.Arrays;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.api.incubator.logs;
package io.opentelemetry.api.common;

import java.util.Objects;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.api.incubator.logs;
package io.opentelemetry.api.common;

import java.util.Objects;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.api.incubator.logs;
package io.opentelemetry.api.common;

import java.util.Objects;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.api.incubator.logs;
package io.opentelemetry.api.common;

/**
* AnyValue type options, mirroring <a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.api.incubator.logs;
package io.opentelemetry.api.common;

/**
* Key-value pair of {@link String} key and {@link AnyValue} value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.api.incubator.logs;
package io.opentelemetry.api.common;

import com.google.auto.value.AutoValue;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.api.incubator.logs;
package io.opentelemetry.api.common;

import static java.util.stream.Collectors.joining;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package io.opentelemetry.api.logs;

import io.opentelemetry.api.common.AnyValue;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.context.Context;
import java.time.Instant;
Expand Down Expand Up @@ -70,6 +71,11 @@ public LogRecordBuilder setBody(String body) {
return this;
}

@Override
public LogRecordBuilder setBody(AnyValue<?> body) {
return this;
}

@Override
public <T> LogRecordBuilder setAttribute(AttributeKey<T> key, T value) {
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package io.opentelemetry.api.logs;

import io.opentelemetry.api.common.AnyValue;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.context.Context;
Expand Down Expand Up @@ -66,9 +67,19 @@
/** Set the severity text. */
LogRecordBuilder setSeverityText(String severityText);

/** Set the body string. */
/**
* Set the body string.
*
* <p>Shorthand for calling {@link #setBody(AnyValue)} with {@link AnyValue#of(String)}.
*/
LogRecordBuilder setBody(String body);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'd rather see this string version have a default implementation that delegates to the AnyValue version, rather than the other way around. It seems safer and less potentially lossy that way.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately we can't do this because. Because of our backwards compatibility guarantees, any new interface method we add has to have a default implementation. The default implementation should do the most sensible thing possible, which in this case is to call the existing setBody(String) method with a string encoding of the anyvalue.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of our backwards compatibility guarantees, any new interface method we add has to have a default implementation.

Note that these are the rules we've been abiding by, but we decided at the spec level that this type of compatibility isn't actually required. It allows alternative API implementations to continue to be able to compile when new API versions come out, but we decided its reasonable to say that alternative API implementations should have to stay up to date with API releases.

See this section of the spec: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md#extending-apisdk-abstractions

I'm inclined to continue with the practices we've been following unless doing so causes some egregious API user experience.


/** Set the body {@link AnyValue}. */
default LogRecordBuilder setBody(AnyValue<?> body) {
setBody(body.asString());
return this;

Check warning on line 80 in api/all/src/main/java/io/opentelemetry/api/logs/LogRecordBuilder.java

View check run for this annotation

Codecov / codecov/patch

api/all/src/main/java/io/opentelemetry/api/logs/LogRecordBuilder.java#L79-L80

Added lines #L79 - L80 were not covered by tests
}

/**
* Sets attributes. If the {@link LogRecordBuilder} previously contained a mapping for any of the
* keys, the old values are replaced by the specified values.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import static org.assertj.core.api.Assertions.assertThatCode;

import io.opentelemetry.api.common.AnyValue;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.context.Context;
Expand All @@ -30,6 +31,7 @@ void buildAndEmit() {
.setSeverity(Severity.DEBUG)
.setSeverityText("debug")
.setBody("body")
.setBody(AnyValue.of("body"))
.setAttribute(AttributeKey.stringKey("key1"), "value1")
.setAllAttributes(Attributes.builder().put("key2", "value2").build())
.emit())
Expand Down
7 changes: 3 additions & 4 deletions api/incubator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ See [EventApiUsageTest](./src/test/java/io/opentelemetry/api/incubator/events/Ev

Features:

* Check if logger is enabled before emitting logs to avoid uneccessary computation
* Set AnyValue log record body with arbitrarily complex data
* Check if logger is enabled before emitting logs to avoid unnecessary computation

See [ExtendedLogsBridgeApiUsageTest](./src/test/java/io/opentelemetry/api/incubator/logs/ExtendedLogsBridgeApiUsageTest.java).

Expand All @@ -31,7 +30,7 @@ See [ExtendedMetricsApiUsageTest](./src/test/java/io/opentelemetry/api/incubator

Features:

* Check if instrument is enabled before recording measurements to avoid uneccessary computation
* Check if instrument is enabled before recording measurements to avoid unnecessary computation
* Simplified injection / extraction of context

See [ExtendedContextPropagatorsUsageTest](./src/test/java/io/opentelemetry/api/incubator/propagation/ExtendedContextPropagatorsUsageTest.java).
Expand All @@ -40,7 +39,7 @@ See [ExtendedContextPropagatorsUsageTest](./src/test/java/io/opentelemetry/api/i

Features:

* Check if tracer is enabled before starting spans to avoid uneccessary computation
* Check if tracer is enabled before starting spans to avoid unnecessary computation
* Utility methods to reduce boilerplace using span API, including extracting context, and wrapping runnables / callables with spans

See [ExtendedTraceApiUsageTest](./src/test/java/io/opentelemetry/api/incubator/trace/ExtendedTraceApiUsageTest.java).
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

package io.opentelemetry.api.incubator.events;

import io.opentelemetry.api.common.AnyValue;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.incubator.logs.AnyValue;
import io.opentelemetry.api.logs.Severity;
import io.opentelemetry.context.Context;
import java.time.Instant;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@

import static java.util.stream.Collectors.toList;

import io.opentelemetry.api.common.AnyValue;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.incubator.logs.AnyValue;
import io.opentelemetry.api.logs.Severity;
import io.opentelemetry.context.Context;
import java.time.Instant;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
/** Extended {@link LogRecordBuilder} with experimental APIs. */
public interface ExtendedLogRecordBuilder extends LogRecordBuilder {

/** Set the body {@link AnyValue}. */
LogRecordBuilder setBody(AnyValue<?> body);
// Nothing at the moment, but experimental methods may be added in the future.

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@

import static org.assertj.core.api.Assertions.assertThatCode;

import io.opentelemetry.api.common.AnyValue;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.incubator.logs.AnyValue;
import io.opentelemetry.api.logs.Severity;
import io.opentelemetry.context.Context;
import java.time.Instant;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;

import com.google.common.collect.ImmutableMap;
import io.opentelemetry.api.common.AnyValue;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.incubator.logs.AnyValue;
import io.opentelemetry.sdk.logs.SdkLoggerProvider;
import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor;
import io.opentelemetry.sdk.logs.internal.AnyValueBody;
import io.opentelemetry.sdk.logs.internal.SdkEventLoggerProvider;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter;
Expand Down Expand Up @@ -64,7 +63,8 @@ void eventApiUsage() {
assertThat(logData)
.hasAttributes(
Attributes.builder().put("event.name", "org.foo.my-event").build());
assertThat(((AnyValueBody) logData.getBody()).asAnyValue())
assertThat(logData.getAnyValueBody())
.isNotNull()
.isEqualTo(
AnyValue.of(
ImmutableMap.of(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.params.provider.Arguments.arguments;

import io.opentelemetry.api.common.AnyValue;
import io.opentelemetry.api.common.AnyValueType;
import io.opentelemetry.api.common.KeyAnyValue;
jack-berg marked this conversation as resolved.
Show resolved Hide resolved
import java.nio.ByteBuffer;
import java.nio.ReadOnlyBufferException;
import java.nio.charset.StandardCharsets;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,14 @@
import static io.opentelemetry.sdk.logs.internal.LoggerConfig.disabled;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;

import com.google.common.collect.ImmutableMap;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.logs.Logger;
import io.opentelemetry.sdk.logs.SdkLoggerProvider;
import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder;
import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor;
import io.opentelemetry.sdk.logs.internal.AnyValueBody;
import io.opentelemetry.sdk.logs.internal.SdkLoggerProviderUtil;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;

/** Demonstrating usage of extended Logs Bridge API. */
Expand Down Expand Up @@ -80,56 +76,4 @@ void loggerEnabled() {
private static String flipCoin() {
return random.nextBoolean() ? "heads" : "tails";
}

@Test
void extendedLogRecordBuilderUsage() {
// Setup SdkLoggerProvider
InMemoryLogRecordExporter exporter = InMemoryLogRecordExporter.create();
SdkLoggerProvider loggerProvider =
SdkLoggerProvider.builder()
// Default resource used for demonstration purposes
.setResource(Resource.getDefault())
// Simple processor w/ in-memory exporter used for demonstration purposes
.addLogRecordProcessor(SimpleLogRecordProcessor.create(exporter))
.build();

// Get a Logger for a scope
Logger logger = loggerProvider.get("org.foo.my-scope");

// Cast to ExtendedLogRecordBuilder, and emit a log
((ExtendedLogRecordBuilder) logger.logRecordBuilder())
// ...can set AnyValue log record body, allowing for arbitrarily complex data
.setBody(
AnyValue.of(
ImmutableMap.of(
"key1",
AnyValue.of("value1"),
"key2",
AnyValue.of(
ImmutableMap.of(
"childKey1",
AnyValue.of("value2"),
"childKey2",
AnyValue.of("value3"))))))
.emit();

// SDK can access AnyValue body by casting to AnyValueBody
loggerProvider.forceFlush().join(10, TimeUnit.SECONDS);
assertThat(exporter.getFinishedLogRecordItems())
.satisfiesExactly(
logData ->
assertThat(((AnyValueBody) logData.getBody()).asAnyValue())
.isEqualTo(
AnyValue.of(
ImmutableMap.of(
"key1",
AnyValue.of("value1"),
"key2",
AnyValue.of(
ImmutableMap.of(
"childKey1",
AnyValue.of("value2"),
"childKey2",
AnyValue.of("value3")))))));
}
}
13 changes: 8 additions & 5 deletions buildSrc/src/main/kotlin/otel.japicmp-conventions.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,21 @@ class AllowNewAbstractMethodOnAutovalueClasses : AbstractRecordingSeenMembers()
override fun maybeAddViolation(member: JApiCompatibility): Violation? {
val allowableAutovalueChanges = setOf(JApiCompatibilityChange.METHOD_ABSTRACT_ADDED_TO_CLASS, JApiCompatibilityChange.METHOD_ADDED_TO_PUBLIC_CLASS)
if (member.compatibilityChanges.filter { !allowableAutovalueChanges.contains(it) }.isEmpty() &&
member is JApiMethod &&
member.getjApiClass().newClass.get().getAnnotation(AutoValue::class.java) != null
) {
member is JApiMethod && isAutoValueClass(member.getjApiClass()))
{
return Violation.accept(member, "Autovalue will automatically add implementation")
}
if (member.compatibilityChanges.isEmpty() &&
member is JApiClass &&
member.newClass.get().getAnnotation(AutoValue::class.java) != null) {
member is JApiClass && isAutoValueClass(member)) {
return Violation.accept(member, "Autovalue class modification is allowed")
}
return null
}

fun isAutoValueClass(japiClass: JApiClass): Boolean {
return japiClass.newClass.get().getAnnotation(AutoValue::class.java) != null ||
japiClass.newClass.get().getAnnotation(AutoValue.Builder::class.java) != null
}
}

class SourceIncompatibleRule : AbstractRecordingSeenMembers() {
Expand Down
Loading
Loading