Repository: open-telemetry/opentelemetry-java
File: exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/logs/LogMarshaler.java (Java)

Code before:
import io.opentelemetry.exporter.internal.marshal.Serializer;
import io.opentelemetry.exporter.internal.otlp.AnyValueMarshaler;
import io.opentelemetry.exporter.internal.otlp.KeyValueMarshaler;
import io.opentelemetry.proto.logs.v1.internal.LogRecord;
import io.opentelemetry.proto.logs.v1.internal.SeverityNumber;
import io.opentelemetry.sdk.logs.data.LogRecordData;
import java.io.IOException;
import javax.annotation.Nullable;

final class LogMarshaler extends MarshalerWithSize {
  private static final String INVALID_TRACE_ID = TraceId.getInvalid();
  private static final String INVALID_SPAN_ID = SpanId.getInvalid();

  private final long timeUnixNano;
  private final long observedTimeUnixNano;
  private final ProtoEnumInfo severityNumber;
  private final byte[] severityText;
  @Nullable private final MarshalerWithSize anyValueMarshaler;
  private final KeyValueMarshaler[] attributeMarshalers;
  private final int droppedAttributesCount;
  private final TraceFlags traceFlags;
  @Nullable private final String traceId;
  @Nullable private final String spanId;

  static LogMarshaler create(LogRecordData logRecordData) {
    KeyValueMarshaler[] attributeMarshalers =
        KeyValueMarshaler.createForAttributes(logRecordData.getAttributes());

    MarshalerWithSize bodyMarshaler =
        logRecordData.getBodyValue() == null
            ? null
            : AnyValueMarshaler.create(logRecordData.getBodyValue());

    SpanContext spanContext = logRecordData.getSpanContext();
    return new LogMarshaler(
        logRecordData.getTimestampEpochNanos(),
        logRecordData.getObservedTimestampEpochNanos(),
        toProtoSeverityNumber(logRecordData.getSeverity()),
        MarshalerUtil.toBytes(logRecordData.getSeverityText()),
        bodyMarshaler,
        attributeMarshalers,
        logRecordData.getTotalAttributeCount() - logRecordData.getAttributes().size(),
        spanContext.getTraceFlags(),
        spanContext.getTraceId().equals(INVALID_TRACE_ID) ? null : spanContext.getTraceId(),
        spanContext.getSpanId().equals(INVALID_SPAN_ID) ? null : spanContext.getSpanId());
  }

  private LogMarshaler(
      long timeUnixNano,
      long observedTimeUnixNano,
      ProtoEnumInfo severityNumber,

Review comment (suggestion):
It's a little inconsistent to me here -- I grok that not every log has a span/trace context, so it makes sense that the `traceId` and `spanId` are `@Nullable`. But isn't the same true of `eventName`? Not every log has an event name, so why not also make it `@Nullable` instead of doing the empty array thing?

Perhaps there's a broader practice regarding protos and arrays and I'm just not familiar with that? Or more likely it's just that we always favor non-null fields and this gets marshaled to length 0 anyway so it doesn't matter....in which case, I also wonder why we didn't do that with empty non-null strings for span/trace ID (or use the invalid constants). Anyway, not a big deal, just made me wonder.

Code after:
import io.opentelemetry.exporter.internal.marshal.Serializer;
import io.opentelemetry.exporter.internal.otlp.AnyValueMarshaler;
import io.opentelemetry.exporter.internal.otlp.KeyValueMarshaler;
import io.opentelemetry.proto.logs.v1.internal.LogRecord;
import io.opentelemetry.proto.logs.v1.internal.SeverityNumber;
import io.opentelemetry.sdk.logs.data.LogRecordData;
import io.opentelemetry.sdk.logs.data.internal.ExtendedLogRecordData;
import java.io.IOException;
import javax.annotation.Nullable;

final class LogMarshaler extends MarshalerWithSize {
  private static final String INVALID_TRACE_ID = TraceId.getInvalid();
  private static final String INVALID_SPAN_ID = SpanId.getInvalid();
  private static final byte[] EMPTY_BYTES = new byte[0];

  private final long timeUnixNano;
  private final long observedTimeUnixNano;
  private final ProtoEnumInfo severityNumber;
  private final byte[] severityText;
  @Nullable private final MarshalerWithSize anyValueMarshaler;
  private final KeyValueMarshaler[] attributeMarshalers;
  private final int droppedAttributesCount;
  private final TraceFlags traceFlags;
  @Nullable private final String traceId;
  @Nullable private final String spanId;
  private final byte[] eventName;

  static LogMarshaler create(LogRecordData logRecordData) {
    KeyValueMarshaler[] attributeMarshalers =
        KeyValueMarshaler.createForAttributes(logRecordData.getAttributes());

    MarshalerWithSize bodyMarshaler =
        logRecordData.getBodyValue() == null
            ? null
            : AnyValueMarshaler.create(logRecordData.getBodyValue());

    SpanContext spanContext = logRecordData.getSpanContext();
    return new LogMarshaler(
        logRecordData.getTimestampEpochNanos(),
        logRecordData.getObservedTimestampEpochNanos(),
        toProtoSeverityNumber(logRecordData.getSeverity()),
        MarshalerUtil.toBytes(logRecordData.getSeverityText()),
        bodyMarshaler,
        attributeMarshalers,
        logRecordData.getTotalAttributeCount() - logRecordData.getAttributes().size(),
        spanContext.getTraceFlags(),
        spanContext.getTraceId().equals(INVALID_TRACE_ID) ? null : spanContext.getTraceId(),
        spanContext.getSpanId().equals(INVALID_SPAN_ID) ? null : spanContext.getSpanId(),
        logRecordData instanceof ExtendedLogRecordData
            ? MarshalerUtil.toBytes(((ExtendedLogRecordData) logRecordData).getEventName())
            : EMPTY_BYTES);