// Copyright 2022 The OpenZipkin Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /* Package zipkin_proto3 adds support for the Zipkin protobuf definition to allow Go applications to consume model.SpanModel from protobuf serialized data. */ package zipkin_proto3 import ( "encoding/binary" "errors" "fmt" "net" "time" "google.golang.org/protobuf/proto" zipkinmodel "github.com/openzipkin/zipkin-go/model" ) // ParseSpans parses zipkinmodel.SpanModel values from data serialized by Protobuf3. // debugWasSet is a boolean that toggles the Debug field of each Span. Its value // is usually retrieved from the transport headers when the "X-B3-Flags" header has a value of 1. func ParseSpans(protoBlob []byte, debugWasSet bool) (zss []*zipkinmodel.SpanModel, err error) { var listOfSpans ListOfSpans if err := proto.Unmarshal(protoBlob, &listOfSpans); err != nil { return nil, err } for _, zps := range listOfSpans.Spans { zms, err := protoSpanToModelSpan(zps, debugWasSet) if err != nil { return zss, err } zss = append(zss, zms) } return zss, nil } var errNilZipkinSpan = errors.New("expecting a non-nil Span") func protoSpanToModelSpan(s *Span, debugWasSet bool) (*zipkinmodel.SpanModel, error) { if s == nil { return nil, errNilZipkinSpan } if len(s.TraceId) != 16 { return nil, fmt.Errorf("invalid TraceID: has length %d yet wanted length 16", len(s.TraceId)) } traceID, err := zipkinmodel.TraceIDFromHex(fmt.Sprintf("%x", s.TraceId)) if err != nil { return nil, fmt.Errorf("invalid TraceID: %v", err) } parentSpanID, _, err := protoSpanIDToModelSpanID(s.ParentId) if err != nil { return nil, fmt.Errorf("invalid ParentID: %v", err) } spanIDPtr, spanIDBlank, err := protoSpanIDToModelSpanID(s.Id) if err != nil { return nil, fmt.Errorf("invalid SpanID: %v", err) } if spanIDBlank || spanIDPtr == nil { // This is a logical error return nil, errors.New("expected a non-nil SpanID") } zmsc := zipkinmodel.SpanContext{ TraceID: traceID, ID: *spanIDPtr, ParentID: parentSpanID, Debug: debugWasSet, } zms := &zipkinmodel.SpanModel{ SpanContext: zmsc, Name: s.Name, Kind: zipkinmodel.Kind(s.Kind.String()), Timestamp: microsToTime(s.Timestamp), Tags: s.Tags, Duration: microsToDuration(s.Duration), LocalEndpoint: protoEndpointToModelEndpoint(s.LocalEndpoint), RemoteEndpoint: protoEndpointToModelEndpoint(s.RemoteEndpoint), Shared: s.Shared, Annotations: protoAnnotationsToModelAnnotations(s.Annotations), } return zms, nil } func microsToDuration(us uint64) time.Duration { // us to ns; ns are the units of Duration return time.Duration(us * 1e3) } func protoEndpointToModelEndpoint(zpe *Endpoint) *zipkinmodel.Endpoint { if zpe == nil { return nil } return &zipkinmodel.Endpoint{ ServiceName: zpe.ServiceName, IPv4: net.IP(zpe.Ipv4), IPv6: net.IP(zpe.Ipv6), Port: uint16(zpe.Port), } } func protoSpanIDToModelSpanID(spanId []byte) (zid *zipkinmodel.ID, blank bool, err error) { if len(spanId) == 0 { return nil, true, nil } if len(spanId) != 8 { return nil, true, fmt.Errorf("has length %d yet wanted length 8", len(spanId)) } // Converting [8]byte --> uint64 u64 := binary.BigEndian.Uint64(spanId) zid_ := zipkinmodel.ID(u64) return &zid_, false, nil } func protoAnnotationsToModelAnnotations(zpa []*Annotation) (zma []zipkinmodel.Annotation) { for _, za := range zpa { if za != nil { zma = append(zma, zipkinmodel.Annotation{ Timestamp: microsToTime(za.Timestamp), Value: za.Value, }) } } if len(zma) == 0 { return nil } return zma } func microsToTime(us uint64) time.Time { return time.Unix(0, int64(us*1e3)).UTC() }