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 LogRecordProcessor to record event log records as span events #1551

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

jack-berg
Copy link
Member

Resolves open-telemetry/opentelemetry-java#6880.

Supersedes open-telemetry/opentelemetry-java#6650.

The event {@link LogRecordData} is converted to attributes on the span event as follows:

  • event.name attribute is mapped to span event name
  • LogRecordData#getTimestampEpochNanos() is mapped to span event timestamp
  • LogRecordData#getAttributes() are mapped to span event attributes, excluding event.name
  • LogRecordData#getObservedTimestampEpochNanos() is mapped to span event attribute with key log.record.observed_timestamp
  • LogRecordData#getSeverity() is mapped to span event attribute with key log.record.severity_number
  • LogRecordData#getBodyValue() is mapped to span event attribute with key log.record.body, as an escaped JSON string following the standard protobuf JSON encoding. NOTE: this results in an encoding that feels more verbose than necessary, but allows us to avoid having to define a new encoding.
  • LogRecordData#getTotalAttributeCount() - LogRecordData#getAttributes().size() is mapped to span event attribute log.record.dropped_attributes_count

@jack-berg
Copy link
Member Author

@breedx-splk, @LikeTheSalad - I'm happy to become a component owner for io.opentelemetry.contrib:opentelemetry-processors.

Copy link
Contributor

@breedx-splk breedx-splk left a comment

Choose a reason for hiding this comment

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

Thanks. This is helpful and I didn't expect it to show up so soon! I had a few small questions/comments but this seems solid.

- event_to_span_event_bridge: {}
```
// TODO(jack-berg): remove "{}" after merging / rle https://github.com/open-telemetry/opentelemetry-java/pull/6891/files
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 this comment (assuming it stays here temporarily) ought to be inside the yaml block.


// For EventToSpanEventBridge
implementation("io.opentelemetry:opentelemetry-exporter-otlp-common")
implementation("com.fasterxml.jackson.core:jackson-core")
Copy link
Contributor

Choose a reason for hiding this comment

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

Where is jackson used? I don't see it. Is it a transitive runtime-only dependency maybe?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah its required to call Marshaler#writeJsonTo(OutputStream)

opentelemetry-exporter-logging-otlp has the same dependency here: https://github.com/open-telemetry/opentelemetry-java/blob/main/exporters/logging-otlp/build.gradle.kts#L19

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok, that's what I was guessing. Can we change this to runtimeOnly() then?

* <li>{@link LogRecordData#getObservedTimestampEpochNanos()} is mapped to span event attribute
* with key {@code log.record.observed_timestamp}
* <li>{@link LogRecordData#getSeverity()} is mapped to span event attribute with key {@code
* log.record.severity_number}
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you like log.record.severity_number better than simply log.record.severity? Maybe you're just trying to maintain consistency with the protos?

Copy link
Member Author

Choose a reason for hiding this comment

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

Maybe you're just trying to maintain consistency with the protos?

Exactly... if / when the spec comes out and tell us how to translate between events and span events, we'll of course follow those instructions. But until then, the less opinionated we can be the better, and following the proto field naming is a way to avoid having an opinion.

https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/logs/v1/logs.proto#L161

*/
public final class EventToSpanEventBridge implements LogRecordProcessor {

private static final Logger LOGGER = Logger.getLogger(EventToSpanEventBridge.class.getName());
Copy link
Contributor

Choose a reason for hiding this comment

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

We use lowercase for these in almost every other place. Also, I know it's inconsistent with the convention of naming static final constants all uppercase....conflicting conventions are at play here.

Suggested change
private static final Logger LOGGER = Logger.getLogger(EventToSpanEventBridge.class.getName());
private static final Logger logger = Logger.getLogger(EventToSpanEventBridge.class.getName());

Value<?> body = logRecord.getBodyValue();
if (body != null) {
MarshalerWithSize marshaler = AnyValueMarshaler.create(body);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd prefer to use the more general type on the left-hand side and then the variable name also gets less cryptic.

Suggested change
ByteArrayOutputStream baos = new ByteArrayOutputStream();
OutputStream out = new ByteArrayOutputStream();

@@ -56,6 +56,7 @@ components:
processors:
- LikeTheSalad
- breedx-splk
- jack-berg
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the help!

Comment on lines +76 to +80
LogRecordData logRecordData = logRecord.toLogRecordData();
String eventName = logRecordData.getAttributes().get(EVENT_NAME);
if (eventName == null) {
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't have to be addressed in this PR, but it's a little disappointing that we have to do the toLogRecordData() conversion first in order to check to see if an attribute exists on the ReadWriteLogRecord. In fact, the ReadWriteLogRecord doesn't allow much reading at all, does it...? /rant

At least we're within onEmit which should only be relevant for events (so that event.name is never actually null) but still... :)

Copy link
Member Author

Choose a reason for hiding this comment

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

It is disappointing and needlessly wasteful. We have a todo to add more getters for directly accessing bits of info in ReadWriteLogRecord. May be time to pick that up..

Separately, I wonder if we can / should add a helper method boolean isEvent() since we anticipate this being a common thing to access.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I like that idea. We can revisit this here later when that happens.

span.end();
}

assertThat(spanExporter.getFinishedSpanItems()).isEmpty();
Copy link
Contributor

Choose a reason for hiding this comment

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

It feels like the goal was to test the isRecording() check/functionality but instead it seems like this is just testing out the sampler. Maybe it's the case that the SamplingResult determines what the implementation of isRecording() returns? In any case, it's not super obvious to the reader why we would be testing that sampling works here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Maybe it's the case that the SamplingResult determines what the implementation of isRecording() returns?

Yes that's right. I can add a comment to help the reader connect these dots.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Option to convert log events to span events
4 participants