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 gogo/protobuf to OpenTelemetry OTLP data model #5067

Merged
merged 15 commits into from
Jan 4, 2024
12 changes: 8 additions & 4 deletions cmd/query/app/apiv3/gateway_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ func makeTestTrace() (*model.Trace, model.TraceID) {
TraceID: traceID,
SpanID: model.NewSpanID(180),
OperationName: "foobar",
Tags: []model.KeyValue{
model.String("span.kind", "server"),
model.Bool("error", true),
},
},
},
}, traceID
Expand Down Expand Up @@ -160,9 +164,9 @@ func (gw *testGateway) runGatewayGetTrace(t *testing.T) {
parseResponse(t, body, &response)

assert.Len(t, response.Result.ResourceSpans, 1)
assert.Equal(t,
assert.EqualValues(t,
bytesOfTraceID(t, traceID.High, traceID.Low),
response.Result.ResourceSpans[0].ScopeSpans[0].Spans[0].TraceId)
response.Result.ResourceSpans[0].ScopeSpans[0].Spans[0].TraceID)
}

func (gw *testGateway) runGatewayFindTraces(t *testing.T) {
Expand All @@ -179,9 +183,9 @@ func (gw *testGateway) runGatewayFindTraces(t *testing.T) {
parseResponse(t, body, &response)

assert.Len(t, response.Result.ResourceSpans, 1)
assert.Equal(t,
assert.EqualValues(t,
bytesOfTraceID(t, traceID.High, traceID.Low),
response.Result.ResourceSpans[0].ScopeSpans[0].Spans[0].TraceId)
response.Result.ResourceSpans[0].ScopeSpans[0].Spans[0].TraceID)
}

func bytesOfTraceID(t *testing.T, high, low uint64) []byte {
Expand Down
11 changes: 0 additions & 11 deletions cmd/query/app/apiv3/http_gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,17 +124,6 @@ func (h *HTTPGateway) returnSpansTestable(
if h.tryHandleError(w, err, http.StatusInternalServerError) {
return
}
for _, rs := range resourceSpans {
for _, ss := range rs.ScopeSpans {
for _, s := range ss.Spans {
if len(s.ParentSpanId) == 0 {
// If ParentSpanId is empty array then gogo/jsonpb renders it as empty string.
// To match the output with grpc-gateway we set it to nil and it won't be included.
s.ParentSpanId = nil
Copy link
Member Author

Choose a reason for hiding this comment

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

field is not nullable so this fix is no longer possible

}
}
}
}
response := &api_v3.GRPCGatewayWrapper{
Result: &api_v3.SpansResponseChunk{
ResourceSpans: resourceSpans,
Expand Down
6 changes: 5 additions & 1 deletion cmd/query/app/apiv3/snapshots/FindTraces.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@
"spans": [
{
"endTimeUnixNano": "11651379494838206464",
"kind": "SPAN_KIND_SERVER",
"name": "foobar",
"parentSpanId": "",
Copy link
Member Author

Choose a reason for hiding this comment

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

Could not find a way to suppress this empty field. In practice not a big deal all but one span in a trace will have non-empty parent. Also, verified that OTLP-JSON also includes empty parent ID.

"spanId": "AAAAAAAAALQ=",
"startTimeUnixNano": "11651379494838206464",
"status": {},
"status": {
"code": "STATUS_CODE_ERROR"
},
Copy link
Member Author

Choose a reason for hiding this comment

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

Added this and span kind as examples of enums. This matches the previous output when using grpc-gateway marshaling (and is different from OTLP-JSON encoding).

"traceId": "AAAAAAAAAJYAAAAAAAAAoA=="
}
]
Expand Down
6 changes: 5 additions & 1 deletion cmd/query/app/apiv3/snapshots/GetTrace.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@
"spans": [
{
"endTimeUnixNano": "11651379494838206464",
"kind": "SPAN_KIND_SERVER",
"name": "foobar",
"parentSpanId": "",
"spanId": "AAAAAAAAALQ=",
"startTimeUnixNano": "11651379494838206464",
"status": {},
"status": {
"code": "STATUS_CODE_ERROR"
},
"traceId": "AAAAAAAAAJYAAAAAAAAAoA=="
}
]
Expand Down
12 changes: 12 additions & 0 deletions model/v2/customtype.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) 2024 The Jaeger Authors.
// SPDX-License-Identifier: Apache-2.0

package model

// gogoCustom is an interface that Gogo expects custom types to implement.
// https://github.com/gogo/protobuf/blob/master/proto/custom_gogo.go#L33
type gogoCustom interface {
Marshal() ([]byte, error)
Unmarshal(data []byte) error
Size() int
}
14 changes: 14 additions & 0 deletions model/v2/empty_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) 2023 The Jaeger Authors.
// SPDX-License-Identifier: Apache-2.0

package model

import (
"testing"

"go.uber.org/goleak"
)

func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
45 changes: 45 additions & 0 deletions model/v2/jsonid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) 2023 The Jaeger Authors.
// Copyright The OpenTelemetry Authors
Copy link
Member Author

Choose a reason for hiding this comment

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

Copied from OTEL, but had to change to marshal IDs as base64, to keep with the original grpc-gateway version.

// SPDX-License-Identifier: Apache-2.0

package model

import (
"encoding/base64"
"fmt"
)

// marshalJSON converts trace id into a base64 string enclosed in quotes.
// Called by Protobuf JSON deserialization.
func marshalJSON(id []byte) ([]byte, error) {
// Plus 2 quote chars at the start and end.
hexLen := base64.StdEncoding.EncodedLen(len(id)) + 2

b := make([]byte, hexLen)
base64.StdEncoding.Encode(b[1:hexLen-1], id)
b[0], b[hexLen-1] = '"', '"'

return b, nil

Check warning on line 22 in model/v2/jsonid.go

View check run for this annotation

Codecov / codecov/patch

model/v2/jsonid.go#L14-L22

Added lines #L14 - L22 were not covered by tests
}

// unmarshalJSON inflates trace id from base64 string, possibly enclosed in quotes.
// Called by Protobuf JSON deserialization.
func unmarshalJSON(dst []byte, src []byte) error {
if l := len(src); l >= 2 && src[0] == '"' && src[l-1] == '"' {
src = src[1 : l-1]
}
nLen := len(src)
if nLen == 0 {
return nil
}

Check warning on line 34 in model/v2/jsonid.go

View check run for this annotation

Codecov / codecov/patch

model/v2/jsonid.go#L27-L34

Added lines #L27 - L34 were not covered by tests

if l := base64.StdEncoding.DecodedLen(nLen); len(dst) != l {
return fmt.Errorf("invalid length for ID '%s', dst=%d, src=%d", string(src), len(dst), l)
}

Check warning on line 38 in model/v2/jsonid.go

View check run for this annotation

Codecov / codecov/patch

model/v2/jsonid.go#L36-L38

Added lines #L36 - L38 were not covered by tests

_, err := base64.StdEncoding.Decode(dst, src)
if err != nil {
return fmt.Errorf("cannot unmarshal ID from string '%s': %w", string(src), err)
}
return nil

Check warning on line 44 in model/v2/jsonid.go

View check run for this annotation

Codecov / codecov/patch

model/v2/jsonid.go#L40-L44

Added lines #L40 - L44 were not covered by tests
}
94 changes: 94 additions & 0 deletions model/v2/spanid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright (c) 2023 The Jaeger Authors.
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package model

import (
"errors"

"github.com/gogo/protobuf/proto"
)

const spanIDSize = 8

var (
errMarshalSpanID = errors.New("marshal: invalid buffer length for SpanID")
errUnmarshalSpanID = errors.New("unmarshal: invalid SpanID length")
)

// SpanID is a custom data type that is used for all span_id fields in OTLP
// Protobuf messages.
type SpanID [spanIDSize]byte

var (
_ proto.Sizer = (*SpanID)(nil)
_ gogoCustom = (*SpanID)(nil)
)

// Size returns the size of the data to serialize.
func (sid SpanID) Size() int {
if sid.IsEmpty() {
return 0
}
return spanIDSize

Check warning on line 34 in model/v2/spanid.go

View check run for this annotation

Codecov / codecov/patch

model/v2/spanid.go#L30-L34

Added lines #L30 - L34 were not covered by tests
}

// IsEmpty returns true if id contains at least one non-zero byte.
func (sid SpanID) IsEmpty() bool {
return sid == [spanIDSize]byte{}

Check warning on line 39 in model/v2/spanid.go

View check run for this annotation

Codecov / codecov/patch

model/v2/spanid.go#L38-L39

Added lines #L38 - L39 were not covered by tests
}

// MarshalTo converts trace ID into a binary representation. Called by Protobuf serialization.
func (sid SpanID) MarshalTo(data []byte) (n int, err error) {
if sid.IsEmpty() {
return 0, nil
}

Check warning on line 46 in model/v2/spanid.go

View check run for this annotation

Codecov / codecov/patch

model/v2/spanid.go#L43-L46

Added lines #L43 - L46 were not covered by tests

if len(data) < spanIDSize {
return 0, errMarshalSpanID
}

Check warning on line 50 in model/v2/spanid.go

View check run for this annotation

Codecov / codecov/patch

model/v2/spanid.go#L48-L50

Added lines #L48 - L50 were not covered by tests

return copy(data, sid[:]), nil

Check warning on line 52 in model/v2/spanid.go

View check run for this annotation

Codecov / codecov/patch

model/v2/spanid.go#L52

Added line #L52 was not covered by tests
}

// Marshal implements gogoCustom.
func (sid *SpanID) Marshal() ([]byte, error) {
return sid[:], nil

Check warning on line 57 in model/v2/spanid.go

View check run for this annotation

Codecov / codecov/patch

model/v2/spanid.go#L56-L57

Added lines #L56 - L57 were not covered by tests
}

// Unmarshal inflates this trace ID from binary representation. Called by Protobuf serialization.
func (sid *SpanID) Unmarshal(data []byte) error {
if len(data) == 0 {
*sid = [spanIDSize]byte{}
return nil
}

Check warning on line 65 in model/v2/spanid.go

View check run for this annotation

Codecov / codecov/patch

model/v2/spanid.go#L61-L65

Added lines #L61 - L65 were not covered by tests

if len(data) != spanIDSize {
return errUnmarshalSpanID
}

Check warning on line 69 in model/v2/spanid.go

View check run for this annotation

Codecov / codecov/patch

model/v2/spanid.go#L67-L69

Added lines #L67 - L69 were not covered by tests

copy(sid[:], data)
return nil

Check warning on line 72 in model/v2/spanid.go

View check run for this annotation

Codecov / codecov/patch

model/v2/spanid.go#L71-L72

Added lines #L71 - L72 were not covered by tests
}

// MarshalJSON converts SpanID into a hex string enclosed in quotes.
func (sid SpanID) MarshalJSON() ([]byte, error) {
if sid.IsEmpty() {
return []byte(`""`), nil
}
return marshalJSON(sid[:])

Check warning on line 80 in model/v2/spanid.go

View check run for this annotation

Codecov / codecov/patch

model/v2/spanid.go#L76-L80

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

// UnmarshalJSON decodes SpanID from hex string, possibly enclosed in quotes.
// Called by Protobuf JSON deserialization.
func (sid *SpanID) UnmarshalJSON(data []byte) error {
// in base64 encoding an 8-byte array is padded to 9 bytes
buf := [spanIDSize + 1]byte{}
if err := unmarshalJSON(buf[:], data); err != nil {
return err
}
*sid = [spanIDSize]byte{}
copy(sid[:], buf[:spanIDSize])
return nil

Check warning on line 93 in model/v2/spanid.go

View check run for this annotation

Codecov / codecov/patch

model/v2/spanid.go#L85-L93

Added lines #L85 - L93 were not covered by tests
}
94 changes: 94 additions & 0 deletions model/v2/traceid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright (c) 2023 The Jaeger Authors.
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package model

import (
"errors"

"github.com/gogo/protobuf/proto"
)

const traceIDSize = 16

var (
errMarshalTraceID = errors.New("marshal: invalid buffer length for TraceID")
errUnmarshalTraceID = errors.New("unmarshal: invalid TraceID length")
)

// TraceID is a custom data type that is used for all trace_id fields in OTLP
// Protobuf messages.
type TraceID [traceIDSize]byte

var (
_ proto.Sizer = (*TraceID)(nil)
_ gogoCustom = (*TraceID)(nil)
)

// Size returns the size of the data to serialize.
func (tid TraceID) Size() int {
if tid.IsEmpty() {
return 0
}
return traceIDSize

Check warning on line 34 in model/v2/traceid.go

View check run for this annotation

Codecov / codecov/patch

model/v2/traceid.go#L30-L34

Added lines #L30 - L34 were not covered by tests
}

// IsEmpty returns true if id contains at leas one non-zero byte.
func (tid TraceID) IsEmpty() bool {
return tid == [traceIDSize]byte{}

Check warning on line 39 in model/v2/traceid.go

View check run for this annotation

Codecov / codecov/patch

model/v2/traceid.go#L38-L39

Added lines #L38 - L39 were not covered by tests
}

// Marshal implements gogoCustom.
func (tid *TraceID) Marshal() ([]byte, error) {
return tid[:], nil

Check warning on line 44 in model/v2/traceid.go

View check run for this annotation

Codecov / codecov/patch

model/v2/traceid.go#L43-L44

Added lines #L43 - L44 were not covered by tests
}

// MarshalTo converts trace ID into a binary representation. Called by Protobuf serialization.
func (tid TraceID) MarshalTo(data []byte) (n int, err error) {
if tid.IsEmpty() {
return 0, nil
}

Check warning on line 51 in model/v2/traceid.go

View check run for this annotation

Codecov / codecov/patch

model/v2/traceid.go#L48-L51

Added lines #L48 - L51 were not covered by tests

if len(data) < traceIDSize {
return 0, errMarshalTraceID
}

Check warning on line 55 in model/v2/traceid.go

View check run for this annotation

Codecov / codecov/patch

model/v2/traceid.go#L53-L55

Added lines #L53 - L55 were not covered by tests

return copy(data, tid[:]), nil

Check warning on line 57 in model/v2/traceid.go

View check run for this annotation

Codecov / codecov/patch

model/v2/traceid.go#L57

Added line #L57 was not covered by tests
}

// Unmarshal inflates this trace ID from binary representation. Called by Protobuf serialization.
func (tid *TraceID) Unmarshal(data []byte) error {
if len(data) == 0 {
*tid = [traceIDSize]byte{}
return nil
}

Check warning on line 65 in model/v2/traceid.go

View check run for this annotation

Codecov / codecov/patch

model/v2/traceid.go#L61-L65

Added lines #L61 - L65 were not covered by tests

if len(data) != traceIDSize {
return errUnmarshalTraceID
}

Check warning on line 69 in model/v2/traceid.go

View check run for this annotation

Codecov / codecov/patch

model/v2/traceid.go#L67-L69

Added lines #L67 - L69 were not covered by tests

copy(tid[:], data)
return nil

Check warning on line 72 in model/v2/traceid.go

View check run for this annotation

Codecov / codecov/patch

model/v2/traceid.go#L71-L72

Added lines #L71 - L72 were not covered by tests
}

// MarshalJSON converts trace id into a hex string enclosed in quotes.
func (tid TraceID) MarshalJSON() ([]byte, error) {
if tid.IsEmpty() {
return []byte(`""`), nil
}
return marshalJSON(tid[:])

Check warning on line 80 in model/v2/traceid.go

View check run for this annotation

Codecov / codecov/patch

model/v2/traceid.go#L76-L80

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

// UnmarshalJSON inflates trace id from hex string, possibly enclosed in quotes.
// Called by Protobuf JSON deserialization.
func (tid *TraceID) UnmarshalJSON(data []byte) error {
// in base64 encoding a 16-byte array is padded to 18 bytes
buf := [traceIDSize + 2]byte{}
if err := unmarshalJSON(buf[:], data); err != nil {
return err
}
*tid = [traceIDSize]byte{}
copy(tid[:], buf[:traceIDSize])
return nil

Check warning on line 93 in model/v2/traceid.go

View check run for this annotation

Codecov / codecov/patch

model/v2/traceid.go#L85-L93

Added lines #L85 - L93 were not covered by tests
}
Loading
Loading