-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Changes from 9 commits
c6a883b
7ab9508
1cd4f31
28dea94
68f56d4
89bc9c8
b8bcda0
b99355d
6ea1365
7c00ad0
41df635
d7475a4
40311ee
d129d61
6134e73
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,10 +9,14 @@ | |
"spans": [ | ||
{ | ||
"endTimeUnixNano": "11651379494838206464", | ||
"kind": "SPAN_KIND_SERVER", | ||
"name": "foobar", | ||
"parentSpanId": "", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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==" | ||
} | ||
] | ||
|
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 | ||
} |
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) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// Copyright (c) 2023 The Jaeger Authors. | ||
// Copyright The OpenTelemetry Authors | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} | ||
|
||
// 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 | ||
} | ||
|
||
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) | ||
} | ||
|
||
_, err := base64.StdEncoding.Decode(dst, src) | ||
if err != nil { | ||
return fmt.Errorf("cannot unmarshal ID from string '%s': %w", string(src), err) | ||
} | ||
return nil | ||
} |
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 | ||
} | ||
|
||
// IsEmpty returns true if id contains at least one non-zero byte. | ||
func (sid SpanID) IsEmpty() bool { | ||
return sid == [spanIDSize]byte{} | ||
} | ||
|
||
// 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 | ||
} | ||
|
||
if len(data) < spanIDSize { | ||
return 0, errMarshalSpanID | ||
} | ||
|
||
return copy(data, sid[:]), nil | ||
} | ||
|
||
// Marshal implements gogoCustom. | ||
func (sid *SpanID) Marshal() ([]byte, error) { | ||
return sid[:], nil | ||
} | ||
|
||
// 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 | ||
} | ||
|
||
if len(data) != spanIDSize { | ||
return errUnmarshalSpanID | ||
} | ||
|
||
copy(sid[:], data) | ||
return nil | ||
} | ||
|
||
// 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[:]) | ||
} | ||
|
||
// 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 | ||
} |
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 | ||
} | ||
|
||
// IsEmpty returns true if id contains at leas one non-zero byte. | ||
func (tid TraceID) IsEmpty() bool { | ||
return tid == [traceIDSize]byte{} | ||
} | ||
|
||
// Marshal implements gogoCustom. | ||
func (tid *TraceID) Marshal() ([]byte, error) { | ||
return tid[:], nil | ||
} | ||
|
||
// 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 | ||
} | ||
|
||
if len(data) < traceIDSize { | ||
return 0, errMarshalTraceID | ||
} | ||
|
||
return copy(data, tid[:]), nil | ||
} | ||
|
||
// 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 | ||
} | ||
|
||
if len(data) != traceIDSize { | ||
return errUnmarshalTraceID | ||
} | ||
|
||
copy(tid[:], data) | ||
return nil | ||
} | ||
|
||
// 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[:]) | ||
} | ||
|
||
// 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 | ||
} |
There was a problem hiding this comment.
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