Skip to content

Commit ed1e0b8

Browse files
committed
tracing: use OTLP JSON instead of protobuf, drop proto/otlp dependency
1 parent 70db099 commit ed1e0b8

5 files changed

Lines changed: 118 additions & 95 deletions

File tree

go.mod

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ require (
2525
github.com/stretchr/testify v1.11.1
2626
github.com/vishvananda/netlink v1.3.1
2727
github.com/vishvananda/netns v0.0.5
28-
go.opentelemetry.io/proto/otlp v1.9.0
2928
golang.org/x/sync v0.20.0
3029
golang.org/x/sys v0.42.0
3130
google.golang.org/grpc v1.80.0
@@ -44,7 +43,6 @@ require (
4443
github.com/gogo/protobuf v1.3.2 // indirect
4544
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
4645
github.com/google/go-cmp v0.7.0 // indirect
47-
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
4846
github.com/josharian/native v1.1.0 // indirect
4947
github.com/klauspost/compress v1.18.5 // indirect
5048
github.com/mdlayher/packet v1.1.2 // indirect
@@ -63,8 +61,8 @@ require (
6361
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect
6462
golang.org/x/net v0.51.0 // indirect
6563
golang.org/x/text v0.34.0 // indirect
66-
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect
6764
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
65+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
6866
gopkg.in/yaml.v3 v3.0.1 // indirect
6967
)
7068

go.sum

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,6 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
9191
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
9292
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
9393
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
94-
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
95-
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
9694
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
9795
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
9896
github.com/insomniacslk/dhcp v0.0.0-20250919081422-f80a1952f48e h1:nu5z6Kg+gMNW6tdqnVjg/QEJ8Nw71IJQqOtWj00XHEU=
@@ -107,8 +105,11 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
107105
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
108106
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
109107
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
108+
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
110109
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
111110
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
111+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
112+
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
112113
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
113114
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
114115
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
@@ -173,8 +174,6 @@ go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9
173174
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
174175
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
175176
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
176-
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
177-
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
178177
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
179178
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
180179
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -240,8 +239,6 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
240239
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
241240
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
242241
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
243-
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4=
244-
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng=
245242
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
246243
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
247244
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=

internal/tracing/flush.go

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,16 @@ package tracing
33
import (
44
"bytes"
55
"context"
6+
"encoding/json"
67
"fmt"
78
"net/http"
89
"net/url"
910
"os"
11+
"strconv"
1012
"sync"
1113
"time"
1214

1315
"github.com/containerd/log"
14-
collectorpb "go.opentelemetry.io/proto/otlp/collector/trace/v1"
15-
commonpb "go.opentelemetry.io/proto/otlp/common/v1"
16-
resourcepb "go.opentelemetry.io/proto/otlp/resource/v1"
17-
tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
18-
"google.golang.org/protobuf/proto"
1916
)
2017

2118
// Flusher collects finished spans and periodically exports them via OTLP HTTP.
@@ -82,37 +79,54 @@ func (f *Flusher) flush(ctx context.Context) error {
8279
return nil
8380
}
8481

85-
protoSpans := make([]*tracepb.Span, 0, len(spans))
82+
otlpSpans := make([]otlpSpan, 0, len(spans))
8683
for _, s := range spans {
87-
protoSpans = append(protoSpans, spanToOTLP(s))
84+
otlpSpans = append(otlpSpans, spanToOTLPJSON(s))
8885
}
8986

90-
req := &collectorpb.ExportTraceServiceRequest{
91-
ResourceSpans: []*tracepb.ResourceSpans{{
92-
Resource: &resourcepb.Resource{
93-
Attributes: []*commonpb.KeyValue{{
87+
req := otlpExportRequest{
88+
ResourceSpans: []otlpResourceSpans{{
89+
Resource: otlpResource{
90+
Attributes: []otlpKeyValue{{
9491
Key: "service.name",
95-
Value: &commonpb.AnyValue{Value: &commonpb.AnyValue_StringValue{StringValue: f.serviceName}},
92+
Value: otlpAnyValue{StringValue: f.serviceName},
9693
}},
9794
},
98-
ScopeSpans: []*tracepb.ScopeSpans{{
99-
Spans: protoSpans,
95+
ScopeSpans: []otlpScopeSpans{{
96+
Spans: otlpSpans,
10097
}},
10198
}},
10299
}
103100

104-
data, err := proto.Marshal(req)
101+
return postOTLP(ctx, f.client, f.endpoint, req)
102+
}
103+
104+
func spanToOTLPJSON(s *Span) otlpSpan {
105+
return otlpSpan{
106+
TraceID: s.TraceID.String(),
107+
SpanID: s.SpanID.String(),
108+
ParentSpanID: s.ParentSpanID.String(),
109+
Name: s.Name,
110+
Kind: 1, // SPAN_KIND_INTERNAL
111+
StartTimeUnixNano: strconv.FormatInt(s.StartTime.UnixNano(), 10),
112+
EndTimeUnixNano: strconv.FormatInt(s.EndTime.UnixNano(), 10),
113+
Status: otlpStatus{Code: 1}, // STATUS_CODE_OK
114+
}
115+
}
116+
117+
func postOTLP(ctx context.Context, client *http.Client, endpoint string, req otlpExportRequest) error {
118+
data, err := json.Marshal(req)
105119
if err != nil {
106120
return fmt.Errorf("marshal OTLP request: %w", err)
107121
}
108122

109-
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, f.endpoint, bytes.NewReader(data))
123+
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(data))
110124
if err != nil {
111125
return fmt.Errorf("create HTTP request: %w", err)
112126
}
113-
httpReq.Header.Set("Content-Type", "application/x-protobuf")
127+
httpReq.Header.Set("Content-Type", "application/json")
114128

115-
resp, err := f.client.Do(httpReq)
129+
resp, err := client.Do(httpReq)
116130
if err != nil {
117131
return fmt.Errorf("send OTLP request: %w", err)
118132
}
@@ -124,19 +138,6 @@ func (f *Flusher) flush(ctx context.Context) error {
124138
return nil
125139
}
126140

127-
func spanToOTLP(s *Span) *tracepb.Span {
128-
return &tracepb.Span{
129-
TraceId: s.TraceID[:],
130-
SpanId: s.SpanID[:],
131-
ParentSpanId: s.ParentSpanID[:],
132-
Name: s.Name,
133-
Kind: tracepb.Span_SPAN_KIND_INTERNAL,
134-
StartTimeUnixNano: uint64(s.StartTime.UnixNano()),
135-
EndTimeUnixNano: uint64(s.EndTime.UnixNano()),
136-
Status: &tracepb.Status{Code: tracepb.Status_STATUS_CODE_OK},
137-
}
138-
}
139-
140141
// OTLPEndpoint returns the OTLP traces endpoint URL derived from
141142
// OTEL_EXPORTER_OTLP_ENDPOINT, or "" if the env var is unset.
142143
func OTLPEndpoint() string {

internal/tracing/otlp.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package tracing
2+
3+
import "encoding/hex"
4+
5+
// OTLP JSON types for ExportTraceServiceRequest.
6+
// See https://opentelemetry.io/docs/specs/otlp/#json-protobuf-encoding
7+
8+
type otlpExportRequest struct {
9+
ResourceSpans []otlpResourceSpans `json:"resourceSpans"`
10+
}
11+
12+
type otlpResourceSpans struct {
13+
Resource otlpResource `json:"resource"`
14+
ScopeSpans []otlpScopeSpans `json:"scopeSpans"`
15+
}
16+
17+
type otlpResource struct {
18+
Attributes []otlpKeyValue `json:"attributes"`
19+
}
20+
21+
type otlpScopeSpans struct {
22+
Spans []otlpSpan `json:"spans"`
23+
}
24+
25+
type otlpSpan struct {
26+
TraceID string `json:"traceId"`
27+
SpanID string `json:"spanId"`
28+
ParentSpanID string `json:"parentSpanId,omitempty"`
29+
Name string `json:"name"`
30+
Kind int `json:"kind"`
31+
StartTimeUnixNano string `json:"startTimeUnixNano"`
32+
EndTimeUnixNano string `json:"endTimeUnixNano"`
33+
Status otlpStatus `json:"status"`
34+
Attributes []otlpKeyValue `json:"attributes,omitempty"`
35+
}
36+
37+
type otlpStatus struct {
38+
Code int `json:"code"`
39+
Message string `json:"message,omitempty"`
40+
}
41+
42+
type otlpKeyValue struct {
43+
Key string `json:"key"`
44+
Value otlpAnyValue `json:"value"`
45+
}
46+
47+
type otlpAnyValue struct {
48+
StringValue string `json:"stringValue"`
49+
}
50+
51+
func hexEncode(b []byte) string {
52+
return hex.EncodeToString(b)
53+
}

internal/tracing/relay.go

Lines changed: 29 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -14,38 +14,32 @@
1414
limitations under the License.
1515
*/
1616

17-
// Package tracing relays OTel spans from a VM to the host.
1817
package tracing
1918

2019
import (
21-
"bytes"
2220
"context"
23-
"fmt"
21+
"encoding/hex"
2422
"net/http"
23+
"strconv"
2524
"time"
2625

2726
"github.com/containerd/log"
28-
collectorpb "go.opentelemetry.io/proto/otlp/collector/trace/v1"
29-
commonpb "go.opentelemetry.io/proto/otlp/common/v1"
30-
resourcepb "go.opentelemetry.io/proto/otlp/resource/v1"
31-
tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
32-
"google.golang.org/protobuf/proto"
3327

3428
tracespb "github.com/containerd/nerdbox/api/services/traces/v1"
3529
)
3630

3731
// ForwardTraces reads spans from the VM trace stream and exports them
38-
// to the OTLP endpoint. hostBootTime is the host wall-clock time captured
39-
// when ttrpc became responsive, used to correct VM-vs-host clock skew.
32+
// to the OTLP endpoint as JSON. hostBootTime is the host wall-clock time
33+
// captured when ttrpc became responsive, used to correct VM-vs-host clock skew.
4034
func ForwardTraces(ctx context.Context, stream tracespb.TTRPCTraces_StreamClient, endpoint string, hostBootTime time.Time) {
4135
client := &http.Client{}
4236

4337
// The VM's RTC has only second-level resolution, so its wall clock
4438
// can be up to ~1s behind the host. We compute the offset from the
45-
// first otelttrpc interceptor span (which is created at the moment
46-
// the first ttrpc RPC reaches the VM — a known sync point with the
47-
// host). hostBootTime was captured on the host at the same logical
48-
// moment (when ttrpc became responsive).
39+
// first interceptor span (which is created at the moment the first
40+
// ttrpc RPC reaches the VM — a known sync point with the host).
41+
// hostBootTime was captured on the host at the same logical moment
42+
// (when ttrpc became responsive).
4943
var clockOffset time.Duration
5044
offsetComputed := false
5145

@@ -63,70 +57,50 @@ func ForwardTraces(ctx context.Context, stream tracespb.TTRPCTraces_StreamClient
6357
log.G(ctx).WithField("offset", clockOffset).Debug("VM clock offset computed")
6458
}
6559

66-
if err := exportSpan(ctx, client, endpoint, span, clockOffset); err != nil {
60+
if err := exportVMSpan(ctx, client, endpoint, span, clockOffset); err != nil {
6761
log.G(ctx).WithError(err).Warn("trace relay export")
6862
}
6963
}
7064
}
7165

72-
func exportSpan(ctx context.Context, client *http.Client, endpoint string, s *tracespb.Span, clockOffset time.Duration) error {
66+
func exportVMSpan(ctx context.Context, client *http.Client, endpoint string, s *tracespb.Span, clockOffset time.Duration) error {
7367
startNano := time.Unix(0, s.StartTimeUnixNano).Add(clockOffset).UnixNano()
7468
endNano := time.Unix(0, s.EndTimeUnixNano).Add(clockOffset).UnixNano()
7569

76-
span := &tracepb.Span{
77-
TraceId: s.TraceID,
78-
SpanId: s.SpanID,
79-
ParentSpanId: s.ParentSpanID,
70+
span := otlpSpan{
71+
TraceID: hex.EncodeToString(s.TraceID),
72+
SpanID: hex.EncodeToString(s.SpanID),
73+
ParentSpanID: hex.EncodeToString(s.ParentSpanID),
8074
Name: s.Name,
81-
Kind: tracepb.Span_SpanKind(s.Kind),
82-
StartTimeUnixNano: uint64(startNano),
83-
EndTimeUnixNano: uint64(endNano),
84-
Status: &tracepb.Status{
85-
Code: tracepb.Status_StatusCode(s.StatusCode),
75+
Kind: int(s.Kind),
76+
StartTimeUnixNano: strconv.FormatInt(startNano, 10),
77+
EndTimeUnixNano: strconv.FormatInt(endNano, 10),
78+
Status: otlpStatus{
79+
Code: int(s.StatusCode),
8680
Message: s.StatusMessage,
8781
},
8882
}
8983

9084
for _, kv := range s.Attributes {
91-
span.Attributes = append(span.Attributes, &commonpb.KeyValue{
85+
span.Attributes = append(span.Attributes, otlpKeyValue{
9286
Key: kv.Key,
93-
Value: &commonpb.AnyValue{Value: &commonpb.AnyValue_StringValue{StringValue: kv.Value}},
87+
Value: otlpAnyValue{StringValue: kv.Value},
9488
})
9589
}
9690

97-
req := &collectorpb.ExportTraceServiceRequest{
98-
ResourceSpans: []*tracepb.ResourceSpans{{
99-
Resource: &resourcepb.Resource{
100-
Attributes: []*commonpb.KeyValue{{
91+
req := otlpExportRequest{
92+
ResourceSpans: []otlpResourceSpans{{
93+
Resource: otlpResource{
94+
Attributes: []otlpKeyValue{{
10195
Key: "service.name",
102-
Value: &commonpb.AnyValue{Value: &commonpb.AnyValue_StringValue{StringValue: "nerdbox-vm"}},
96+
Value: otlpAnyValue{StringValue: "nerdbox-vm"},
10397
}},
10498
},
105-
ScopeSpans: []*tracepb.ScopeSpans{{
106-
Spans: []*tracepb.Span{span},
99+
ScopeSpans: []otlpScopeSpans{{
100+
Spans: []otlpSpan{span},
107101
}},
108102
}},
109103
}
110104

111-
data, err := proto.Marshal(req)
112-
if err != nil {
113-
return fmt.Errorf("marshal OTLP request: %w", err)
114-
}
115-
116-
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(data))
117-
if err != nil {
118-
return fmt.Errorf("create HTTP request: %w", err)
119-
}
120-
httpReq.Header.Set("Content-Type", "application/x-protobuf")
121-
122-
resp, err := client.Do(httpReq)
123-
if err != nil {
124-
return fmt.Errorf("send OTLP request: %w", err)
125-
}
126-
resp.Body.Close()
127-
128-
if resp.StatusCode >= 400 {
129-
return fmt.Errorf("OTLP export failed: %s", resp.Status)
130-
}
131-
return nil
105+
return postOTLP(ctx, client, endpoint, req)
132106
}

0 commit comments

Comments
 (0)