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

Add getters/accessors for readable fields in ReadWriteLogRecord. #6924

Merged
merged 11 commits into from
Dec 19, 2024
21 changes: 20 additions & 1 deletion docs/apidiffs/current_vs_latest/opentelemetry-sdk-logs.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,21 @@
Comparing source compatibility of opentelemetry-sdk-logs-1.46.0-SNAPSHOT.jar against opentelemetry-sdk-logs-1.45.0.jar
No changes.
*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.sdk.logs.ReadWriteLogRecord (not serializable)
=== CLASS FILE FORMAT VERSION: 52.0 <- 52.0
+++ NEW INTERFACE: io.opentelemetry.sdk.logs.data.LogRecordData
+++ NEW METHOD: PUBLIC(+) java.lang.Object getAttribute(io.opentelemetry.api.common.AttributeKey<T>)
+++ NEW ANNOTATION: javax.annotation.Nullable
GENERIC TEMPLATES: +++ T:java.lang.Object
+++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.Attributes getAttributes()
+++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.logs.data.Body getBody()
+++ NEW ANNOTATION: java.lang.Deprecated
+++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.common.Value<?> getBodyValue()
+++ NEW ANNOTATION: javax.annotation.Nullable
+++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.common.InstrumentationScopeInfo getInstrumentationScopeInfo()
+++ NEW METHOD: PUBLIC(+) long getObservedTimestampEpochNanos()
+++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.resources.Resource getResource()
+++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.logs.Severity getSeverity()
+++ NEW METHOD: PUBLIC(+) java.lang.String getSeverityText()
+++ NEW ANNOTATION: javax.annotation.Nullable
+++ NEW METHOD: PUBLIC(+) io.opentelemetry.api.trace.SpanContext getSpanContext()
+++ NEW METHOD: PUBLIC(+) long getTimestampEpochNanos()
+++ NEW METHOD: PUBLIC(+) int getTotalAttributeCount()
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,20 @@

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.Value;
import io.opentelemetry.api.logs.Severity;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
import io.opentelemetry.sdk.logs.data.LogRecordData;
import io.opentelemetry.sdk.resources.Resource;
import javax.annotation.Nullable;

/**
* A log record that can be read from and written to.
*
* @since 1.27.0
*/
public interface ReadWriteLogRecord {
public interface ReadWriteLogRecord extends LogRecordData {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure what we think about this .... it's not entirely necessary, but felt more like duplication without it. If we remove this extension, then we could remove the deprecated getBody() method from this interface, but it feels a tad more fragile to me. If we keep it, then we could probably remove the redundant/duplicated javadoc.

Thoughts?

Choose a reason for hiding this comment

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

I'm not sure about it. On one hand it looks reasonable, on the other hand it seems odd to have toLogRecordData() method in the interface that extends LogRecordData - it is like having a method in subclass that converts it to the superclass. It is a bit confusing to me, so I'd vote for not adding 'extends LogRecordData'.
Actually, maybe we should rename ReadWriteLogRecord to avoid this feeling of parent/child relationship between these two interfaces...

Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is correct. We should aim to be symmetric to ReadWriteSpan, which inherits its accessors by extending ReadableSpan.

The notable differences between ReadWriteLogRecord / ReadWriteSpan and LogRecordData / SpanData are:

  • The {Signal}Data interfaces are annotated immutable, while the ReadWrite{Signal} are not.
  • The {Signal}Data interfaces provide access to Resource while ReadWrite{Signal} do not
  • The {Signal}Data interfaces allow you to determine how many attributes were dropped due to limits while ReadWrite{Signal} do not.

In retrospect, maybe there could have been a single interface instead, but now we have to contend with consistency. I think the way to go is to extend the existing patterns consistently, and independently consider whether there's a better API design. But let's separate the issues.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we're trying to be forcibly consistent between signals, then I think we'd want

public interface ReadWriteLogRecord extends LogRecord, ReadableLogRecord {}

so that we're consistent with

public interface ReadWriteSpan extends Span, ReadableSpan {}

One challenge with that right now is that we have NEITHER LogRecord nor ReadableLogRecord interfaces. For consistency, we would need LogRecord to live in the api module and provide the "write-only" methods and ReadableLogRecord would live in the sdk and provide the getters.

Do we think the the expansion of the logging api is within scope for this?

Copy link
Member

Choose a reason for hiding this comment

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

We want to be symmetric to the extent it makes sense. Spans are mutable onStart, and read only onEnd, hence the ReadableLogRecord, ReadWriteLogRecord. The separation of start and end also causes spans to have a dedicated API in Span. These concepts are relevant for logs: Logs constructed and emitted in an atomic operation so no need for a LogRecord interface analogous to Span. And there is only one processor callback which is able to read and write, so no need for a ReadableLogRecord analogous to ReadableSpan.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think you missed my question, which was "Do we think the expansion of the logging api is within scope for this effort?". This effort being: Adding the ability to get data out of ReadWriteLogRecord without having to convert via toLogRecordData(). I ask this because it appears that we would need to extend the logging api with new interfaces in order to make ReadWriteLogRecord consistent with ReadWriteSpan.

We should aim to be symmetric to ReadWriteSpan,

Right. Sure. I can buy into that idea...but I'm kinda surprised that you started discussing start/end/lifecycle and processor callback concerns as part of this. In this PR I'm only concerned with the data access aspects of these interfaces, and not the lifecycle nor action methods. I think it's super weird that you suggest that ReadableSpan exists only in service of SpanProcessor....let's not redefine "what it is" with "how it is used". It's right there in the name -- a ReadableSpan is a span whose internal data/state can be read. That's all.

I think the way to go is to extend the existing patterns consistently, and independently consider whether there's a better API design.

I'm open to this, but I think it requires adding interfaces in the logging api module. Do you want that as part of this PR?

Copy link
Member

Choose a reason for hiding this comment

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

Do we think the expansion of the logging api is within scope for this effort?

No. I think for the purposes of #6895 we want ReadWriteLogRecord to have the capabilities of ReadWriteSpan for reading data without a symmetric interface hierarchy.

In this PR I'm only concerned with the data access aspects of these interfaces, and not the lifecycle nor action methods.

The lifecycle aspects matter because the design of the span interfaces ReadWriteSpan / ReadableSpan / Span directly fall out of the SpanProcessor callbacks and the Span API requirements. Need to compare the LogRecordProcessor callbacks and log API to understand where symmetry makes sense and does not.

I think it's super weird that you suggest that ReadableSpan exists only in service of SpanProcessor....let's not redefine "what it is" with "how it is used". It's right there in the name -- a ReadableSpan is a span whose internal data/state can be read.

The only place ReadableSpan occurs in the SDK's API is in SpanProcessor#onEnd. If SpanProcessor didn't have a requirement for a read-only span onEnd, ReadableSpan wouldn't exist. And so we named it something appropriate reflecting "what it is", but we can't loose sight of "why it exists". I don't think it makes sense to invent interfaces that don't serve any expressed purpose in the API.

public interface ReadWriteLogRecord extends LogRecordData

One additional comment on the original proposal: we can't have ReadWriteLogRecord extend LogRecordData because LogRecordData is annotated @Immutable, and ReadWriteLogRecord violates this.


/**
* Sets an attribute on the log record. If the log record previously contained a mapping for the
Expand Down Expand Up @@ -47,7 +53,87 @@
/** Return an immutable {@link LogRecordData} instance representing this log record. */
LogRecordData toLogRecordData();

// TODO: add additional log record accessors. Currently, all fields can be accessed indirectly via
// #toLogRecordData() at the expense of additional allocations.
/**
* Returns the value of a given attribute if it exists. This is the equivalent of calling
* getAttributes().get(key)
*/
@Nullable
default <T> T getAttribute(AttributeKey<T> key) {
return toLogRecordData().getAttributes().get(key);

Check warning on line 62 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java#L62

Added line #L62 was not covered by tests

Choose a reason for hiding this comment

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

Maybe it would be worth adding some unit tests for these default methods? It could be beneficial for possible future classes that do not overwrite them (or overwrite them partially)

}

/** Returns the resource of this log record. */
@Override
default Resource getResource() {
breedx-splk marked this conversation as resolved.
Show resolved Hide resolved
return toLogRecordData().getResource();

Check warning on line 68 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java#L68

Added line #L68 was not covered by tests
}

/** Returns the instrumentation scope that generated this log. */
@Override
default InstrumentationScopeInfo getInstrumentationScopeInfo() {
return toLogRecordData().getInstrumentationScopeInfo();

Check warning on line 74 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java#L74

Added line #L74 was not covered by tests
}

/** Returns the timestamp at which the log record occurred, in epoch nanos. */
@Override
default long getTimestampEpochNanos() {
return toLogRecordData().getTimestampEpochNanos();

Check warning on line 80 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java#L80

Added line #L80 was not covered by tests
}

/** Returns the timestamp at which the log record was observed, in epoch nanos. */
@Override
default long getObservedTimestampEpochNanos() {
return toLogRecordData().getTimestampEpochNanos();

Check warning on line 86 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java#L86

Added line #L86 was not covered by tests
}

/** Return the span context for this log, or {@link SpanContext#getInvalid()} if unset. */
@Override
default SpanContext getSpanContext() {
return toLogRecordData().getSpanContext();

Check warning on line 92 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java#L92

Added line #L92 was not covered by tests
}

/** Returns the severity for this log, or {@link Severity#UNDEFINED_SEVERITY_NUMBER} if unset. */
@Override
default Severity getSeverity() {
return toLogRecordData().getSeverity();

Check warning on line 98 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java#L98

Added line #L98 was not covered by tests
}

/** Returns the severity text for this log, or null if unset. */
@Nullable
@Override
default String getSeverityText() {
return toLogRecordData().getSeverityText();

Check warning on line 105 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java#L105

Added line #L105 was not covered by tests
}

@Deprecated
@Override
@SuppressWarnings("deprecation") // Implementation of deprecated method
default io.opentelemetry.sdk.logs.data.Body getBody() {
breedx-splk marked this conversation as resolved.
Show resolved Hide resolved
return toLogRecordData().getBody();

Check warning on line 112 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java#L112

Added line #L112 was not covered by tests
}

/** Returns the {@link Value} representation of the log body, of null if unset. */
@Nullable
@Override
default Value<?> getBodyValue() {
return toLogRecordData().getBodyValue();

Check warning on line 119 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java#L119

Added line #L119 was not covered by tests
}

/** Returns the attributes for this log, or {@link Attributes#empty()} if unset. */
@Override
default Attributes getAttributes() {
breedx-splk marked this conversation as resolved.
Show resolved Hide resolved
return toLogRecordData().getAttributes();

Check warning on line 125 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java#L125

Added line #L125 was not covered by tests
}

/**
* Returns the total number of attributes that were recorded on this log.
*
* <p>This number may be larger than the number of attributes that are attached to this log, if
* the total number recorded was greater than the configured maximum value. See {@link
* LogLimits#getMaxNumberOfAttributes()}.
*/
@Override
default int getTotalAttributeCount() {
breedx-splk marked this conversation as resolved.
Show resolved Hide resolved
return toLogRecordData().getTotalAttributeCount();

Check warning on line 137 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/ReadWriteLogRecord.java#L137

Added line #L137 was not covered by tests
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,72 @@
attributes == null ? 0 : attributes.getTotalAddedValues());
}
}

@Override
public Resource getResource() {
return resource;

Check warning on line 131 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java#L131

Added line #L131 was not covered by tests
}

@Override
public InstrumentationScopeInfo getInstrumentationScopeInfo() {
return instrumentationScopeInfo;

Check warning on line 136 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java#L136

Added line #L136 was not covered by tests
}

@Override
public long getTimestampEpochNanos() {
return timestampEpochNanos;

Check warning on line 141 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java#L141

Added line #L141 was not covered by tests
}

@Override
public long getObservedTimestampEpochNanos() {
return observedTimestampEpochNanos;

Check warning on line 146 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java#L146

Added line #L146 was not covered by tests
}

@Override
public SpanContext getSpanContext() {
return spanContext;

Check warning on line 151 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java#L151

Added line #L151 was not covered by tests
}

@Override
public Severity getSeverity() {
return severity;

Check warning on line 156 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java#L156

Added line #L156 was not covered by tests
}

@Nullable
@Override
public String getSeverityText() {
return severityText;

Check warning on line 162 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java#L162

Added line #L162 was not covered by tests
}

@Nullable
@Override
public Value<?> getBodyValue() {
return body;

Check warning on line 168 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java#L168

Added line #L168 was not covered by tests
}

@Override
public Attributes getAttributes() {
breedx-splk marked this conversation as resolved.
Show resolved Hide resolved
return getImmutableAttributes();

Check warning on line 173 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java#L173

Added line #L173 was not covered by tests
}

@Nullable
@Override
public <T> T getAttribute(AttributeKey<T> key) {
synchronized (lock) {

Check warning on line 179 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java#L179

Added line #L179 was not covered by tests
if (attributes == null || attributes.isEmpty()) {
return null;

Check warning on line 181 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java#L181

Added line #L181 was not covered by tests
}
return attributes.get(key);

Check warning on line 183 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java#L183

Added line #L183 was not covered by tests
}
}

@Override
public int getTotalAttributeCount() {
synchronized (lock) {

Check warning on line 189 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java#L189

Added line #L189 was not covered by tests
if (attributes == null || attributes.isEmpty()) {
return 0;

Check warning on line 191 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java#L191

Added line #L191 was not covered by tests
}
return attributes.size();

Check warning on line 193 in sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java

View check run for this annotation

Codecov / codecov/patch

sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkReadWriteLogRecord.java#L193

Added line #L193 was not covered by tests
}
}
}
Loading