2020-03-24 07:41:10 +02:00
|
|
|
// Copyright The OpenTelemetry 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.
|
|
|
|
|
2020-03-11 23:49:02 +02:00
|
|
|
package zipkin
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/binary"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
zkmodel "github.com/openzipkin/zipkin-go/model"
|
|
|
|
|
|
|
|
"go.opentelemetry.io/otel/api/trace"
|
2020-08-18 05:25:03 +02:00
|
|
|
"go.opentelemetry.io/otel/label"
|
2020-03-11 23:49:02 +02:00
|
|
|
export "go.opentelemetry.io/otel/sdk/export/trace"
|
|
|
|
)
|
|
|
|
|
2020-04-19 08:02:29 +02:00
|
|
|
func toZipkinSpanModels(batch []*export.SpanData, serviceName string) []zkmodel.SpanModel {
|
2020-03-11 23:49:02 +02:00
|
|
|
models := make([]zkmodel.SpanModel, 0, len(batch))
|
|
|
|
for _, data := range batch {
|
2020-04-19 08:02:29 +02:00
|
|
|
models = append(models, toZipkinSpanModel(data, serviceName))
|
2020-03-11 23:49:02 +02:00
|
|
|
}
|
|
|
|
return models
|
|
|
|
}
|
|
|
|
|
2020-04-19 08:02:29 +02:00
|
|
|
func toZipkinSpanModel(data *export.SpanData, serviceName string) zkmodel.SpanModel {
|
2020-03-11 23:49:02 +02:00
|
|
|
return zkmodel.SpanModel{
|
2020-04-19 08:02:29 +02:00
|
|
|
SpanContext: toZipkinSpanContext(data),
|
|
|
|
Name: data.Name,
|
|
|
|
Kind: toZipkinKind(data.SpanKind),
|
|
|
|
Timestamp: data.StartTime,
|
|
|
|
Duration: data.EndTime.Sub(data.StartTime),
|
|
|
|
Shared: false,
|
|
|
|
LocalEndpoint: &zkmodel.Endpoint{
|
|
|
|
ServiceName: serviceName,
|
|
|
|
},
|
2020-03-11 23:49:02 +02:00
|
|
|
RemoteEndpoint: nil, // *Endpoint
|
|
|
|
Annotations: toZipkinAnnotations(data.MessageEvents),
|
|
|
|
Tags: toZipkinTags(data),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func toZipkinSpanContext(data *export.SpanData) zkmodel.SpanContext {
|
|
|
|
return zkmodel.SpanContext{
|
|
|
|
TraceID: toZipkinTraceID(data.SpanContext.TraceID),
|
|
|
|
ID: toZipkinID(data.SpanContext.SpanID),
|
|
|
|
ParentID: toZipkinParentID(data.ParentSpanID),
|
|
|
|
Debug: false,
|
|
|
|
Sampled: nil,
|
|
|
|
Err: nil,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-02 14:23:09 +02:00
|
|
|
func toZipkinTraceID(traceID trace.ID) zkmodel.TraceID {
|
2020-03-11 23:49:02 +02:00
|
|
|
return zkmodel.TraceID{
|
|
|
|
High: binary.BigEndian.Uint64(traceID[:8]),
|
|
|
|
Low: binary.BigEndian.Uint64(traceID[8:]),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-02 14:17:11 +02:00
|
|
|
func toZipkinID(spanID trace.SpanID) zkmodel.ID {
|
2020-03-11 23:49:02 +02:00
|
|
|
return zkmodel.ID(binary.BigEndian.Uint64(spanID[:]))
|
|
|
|
}
|
|
|
|
|
2020-05-02 14:17:11 +02:00
|
|
|
func toZipkinParentID(spanID trace.SpanID) *zkmodel.ID {
|
2020-03-11 23:49:02 +02:00
|
|
|
if spanID.IsValid() {
|
|
|
|
id := toZipkinID(spanID)
|
|
|
|
return &id
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func toZipkinKind(kind trace.SpanKind) zkmodel.Kind {
|
|
|
|
switch kind {
|
|
|
|
case trace.SpanKindUnspecified:
|
|
|
|
return zkmodel.Undetermined
|
|
|
|
case trace.SpanKindInternal:
|
|
|
|
// The spec says we should set the kind to nil, but
|
|
|
|
// the model does not allow that.
|
|
|
|
return zkmodel.Undetermined
|
|
|
|
case trace.SpanKindServer:
|
|
|
|
return zkmodel.Server
|
|
|
|
case trace.SpanKindClient:
|
|
|
|
return zkmodel.Client
|
|
|
|
case trace.SpanKindProducer:
|
|
|
|
return zkmodel.Producer
|
|
|
|
case trace.SpanKindConsumer:
|
|
|
|
return zkmodel.Consumer
|
|
|
|
}
|
|
|
|
return zkmodel.Undetermined
|
|
|
|
}
|
|
|
|
|
|
|
|
func toZipkinAnnotations(events []export.Event) []zkmodel.Annotation {
|
|
|
|
if len(events) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
annotations := make([]zkmodel.Annotation, 0, len(events))
|
|
|
|
for _, event := range events {
|
|
|
|
value := event.Name
|
|
|
|
if len(event.Attributes) > 0 {
|
|
|
|
jsonString := attributesToJSONMapString(event.Attributes)
|
|
|
|
if jsonString != "" {
|
|
|
|
value = fmt.Sprintf("%s: %s", event.Name, jsonString)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
annotations = append(annotations, zkmodel.Annotation{
|
|
|
|
Timestamp: event.Time,
|
|
|
|
Value: value,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return annotations
|
|
|
|
}
|
|
|
|
|
2020-08-18 05:25:03 +02:00
|
|
|
func attributesToJSONMapString(attributes []label.KeyValue) string {
|
2020-03-11 23:49:02 +02:00
|
|
|
m := make(map[string]interface{}, len(attributes))
|
|
|
|
for _, attribute := range attributes {
|
|
|
|
m[(string)(attribute.Key)] = attribute.Value.AsInterface()
|
|
|
|
}
|
|
|
|
// if an error happens, the result will be an empty string
|
|
|
|
jsonBytes, _ := json.Marshal(m)
|
|
|
|
return (string)(jsonBytes)
|
|
|
|
}
|
|
|
|
|
|
|
|
func toZipkinTags(data *export.SpanData) map[string]string {
|
|
|
|
// +2 for status code and for status message
|
|
|
|
m := make(map[string]string, len(data.Attributes)+2)
|
|
|
|
for _, kv := range data.Attributes {
|
|
|
|
m[(string)(kv.Key)] = kv.Value.Emit()
|
|
|
|
}
|
|
|
|
if v, ok := m["error"]; ok && v == "false" {
|
|
|
|
delete(m, "error")
|
|
|
|
}
|
|
|
|
m["ot.status_code"] = data.StatusCode.String()
|
|
|
|
m["ot.status_description"] = data.StatusMessage
|
|
|
|
return m
|
|
|
|
}
|