2017-08-22 00:24:54 +00:00
|
|
|
package jsonV1
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"strconv"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/influxdata/telegraf/plugins/inputs/zipkin/codec"
|
2018-06-19 18:55:38 +00:00
|
|
|
"github.com/openzipkin/zipkin-go-opentracing/thrift/gen-go/zipkincore"
|
2017-08-22 00:24:54 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// JSON decodes spans from bodies `POST`ed to the spans endpoint
|
|
|
|
type JSON struct{}
|
|
|
|
|
|
|
|
// Decode unmarshals and validates the JSON body
|
|
|
|
func (j *JSON) Decode(octets []byte) ([]codec.Span, error) {
|
|
|
|
var spans []span
|
|
|
|
err := json.Unmarshal(octets, &spans)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
res := make([]codec.Span, len(spans))
|
|
|
|
for i := range spans {
|
|
|
|
if err := spans[i].Validate(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
res[i] = &spans[i]
|
|
|
|
}
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type span struct {
|
|
|
|
TraceID string `json:"traceId"`
|
|
|
|
SpanName string `json:"name"`
|
|
|
|
ParentID string `json:"parentId,omitempty"`
|
|
|
|
ID string `json:"id"`
|
|
|
|
Time *int64 `json:"timestamp,omitempty"`
|
|
|
|
Dur *int64 `json:"duration,omitempty"`
|
|
|
|
Debug bool `json:"debug,omitempty"`
|
|
|
|
Anno []annotation `json:"annotations"`
|
|
|
|
BAnno []binaryAnnotation `json:"binaryAnnotations"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *span) Validate() error {
|
|
|
|
var err error
|
|
|
|
check := func(f func() (string, error)) {
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
_, err = f()
|
|
|
|
}
|
|
|
|
|
|
|
|
check(s.Trace)
|
|
|
|
check(s.SpanID)
|
|
|
|
check(s.Parent)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = s.BinaryAnnotations()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *span) Trace() (string, error) {
|
|
|
|
if s.TraceID == "" {
|
|
|
|
return "", fmt.Errorf("Trace ID cannot be null")
|
|
|
|
}
|
|
|
|
return TraceIDFromString(s.TraceID)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *span) SpanID() (string, error) {
|
|
|
|
if s.ID == "" {
|
|
|
|
return "", fmt.Errorf("Span ID cannot be null")
|
|
|
|
}
|
|
|
|
return IDFromString(s.ID)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *span) Parent() (string, error) {
|
|
|
|
if s.ParentID == "" {
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
return IDFromString(s.ParentID)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *span) Name() string {
|
|
|
|
return s.SpanName
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *span) Annotations() []codec.Annotation {
|
|
|
|
res := make([]codec.Annotation, len(s.Anno))
|
|
|
|
for i := range s.Anno {
|
|
|
|
res[i] = &s.Anno[i]
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *span) BinaryAnnotations() ([]codec.BinaryAnnotation, error) {
|
|
|
|
res := make([]codec.BinaryAnnotation, len(s.BAnno))
|
|
|
|
for i, a := range s.BAnno {
|
|
|
|
if a.Key() != "" && a.Value() == "" {
|
|
|
|
return nil, fmt.Errorf("No value for key %s at binaryAnnotations[%d]", a.K, i)
|
|
|
|
}
|
|
|
|
if a.Value() != "" && a.Key() == "" {
|
|
|
|
return nil, fmt.Errorf("No key at binaryAnnotations[%d]", i)
|
|
|
|
}
|
|
|
|
res[i] = &s.BAnno[i]
|
|
|
|
}
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *span) Timestamp() time.Time {
|
|
|
|
if s.Time == nil {
|
|
|
|
return time.Time{}
|
|
|
|
}
|
|
|
|
return codec.MicroToTime(*s.Time)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *span) Duration() time.Duration {
|
|
|
|
if s.Dur == nil {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return time.Duration(*s.Dur) * time.Microsecond
|
|
|
|
}
|
|
|
|
|
|
|
|
type annotation struct {
|
|
|
|
Endpoint *endpoint `json:"endpoint,omitempty"`
|
|
|
|
Time int64 `json:"timestamp"`
|
|
|
|
Val string `json:"value,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *annotation) Timestamp() time.Time {
|
|
|
|
return codec.MicroToTime(a.Time)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *annotation) Value() string {
|
|
|
|
return a.Val
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *annotation) Host() codec.Endpoint {
|
|
|
|
return a.Endpoint
|
|
|
|
}
|
|
|
|
|
|
|
|
type binaryAnnotation struct {
|
|
|
|
K string `json:"key"`
|
|
|
|
V json.RawMessage `json:"value"`
|
|
|
|
Type string `json:"type"`
|
|
|
|
Endpoint *endpoint `json:"endpoint,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *binaryAnnotation) Key() string {
|
|
|
|
return b.K
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *binaryAnnotation) Value() string {
|
|
|
|
t, err := zipkincore.AnnotationTypeFromString(b.Type)
|
|
|
|
// Assume this is a string if we cannot tell the type
|
|
|
|
if err != nil {
|
|
|
|
t = zipkincore.AnnotationType_STRING
|
|
|
|
}
|
|
|
|
|
|
|
|
switch t {
|
|
|
|
case zipkincore.AnnotationType_BOOL:
|
|
|
|
var v bool
|
|
|
|
err := json.Unmarshal(b.V, &v)
|
|
|
|
if err == nil {
|
|
|
|
return strconv.FormatBool(v)
|
|
|
|
}
|
|
|
|
case zipkincore.AnnotationType_BYTES:
|
|
|
|
return string(b.V)
|
|
|
|
case zipkincore.AnnotationType_I16, zipkincore.AnnotationType_I32, zipkincore.AnnotationType_I64:
|
|
|
|
var v int64
|
|
|
|
err := json.Unmarshal(b.V, &v)
|
|
|
|
if err == nil {
|
|
|
|
return strconv.FormatInt(v, 10)
|
|
|
|
}
|
|
|
|
case zipkincore.AnnotationType_DOUBLE:
|
|
|
|
var v float64
|
|
|
|
err := json.Unmarshal(b.V, &v)
|
|
|
|
if err == nil {
|
|
|
|
return strconv.FormatFloat(v, 'f', -1, 64)
|
|
|
|
}
|
|
|
|
case zipkincore.AnnotationType_STRING:
|
|
|
|
var v string
|
|
|
|
err := json.Unmarshal(b.V, &v)
|
|
|
|
if err == nil {
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *binaryAnnotation) Host() codec.Endpoint {
|
|
|
|
return b.Endpoint
|
|
|
|
}
|
|
|
|
|
|
|
|
type endpoint struct {
|
|
|
|
ServiceName string `json:"serviceName"`
|
|
|
|
Ipv4 string `json:"ipv4"`
|
|
|
|
Ipv6 string `json:"ipv6,omitempty"`
|
|
|
|
Port int `json:"port"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *endpoint) Host() string {
|
|
|
|
if e.Port != 0 {
|
|
|
|
return fmt.Sprintf("%s:%d", e.Ipv4, e.Port)
|
|
|
|
}
|
|
|
|
return e.Ipv4
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *endpoint) Name() string {
|
|
|
|
return e.ServiceName
|
|
|
|
}
|
|
|
|
|
|
|
|
// TraceIDFromString creates a TraceID from a hexadecimal string
|
|
|
|
func TraceIDFromString(s string) (string, error) {
|
|
|
|
var hi, lo uint64
|
|
|
|
var err error
|
|
|
|
if len(s) > 32 {
|
|
|
|
return "", fmt.Errorf("TraceID cannot be longer than 32 hex characters: %s", s)
|
|
|
|
} else if len(s) > 16 {
|
|
|
|
hiLen := len(s) - 16
|
|
|
|
if hi, err = strconv.ParseUint(s[0:hiLen], 16, 64); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if lo, err = strconv.ParseUint(s[hiLen:], 16, 64); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if lo, err = strconv.ParseUint(s, 16, 64); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if hi == 0 {
|
|
|
|
return fmt.Sprintf("%x", lo), nil
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("%x%016x", hi, lo), nil
|
|
|
|
}
|
|
|
|
|
2017-11-20 22:19:32 +00:00
|
|
|
// IDFromString validates the ID and returns it in hexadecimal format.
|
2017-08-22 00:24:54 +00:00
|
|
|
func IDFromString(s string) (string, error) {
|
|
|
|
if len(s) > 16 {
|
|
|
|
return "", fmt.Errorf("ID cannot be longer than 16 hex characters: %s", s)
|
|
|
|
}
|
|
|
|
id, err := strconv.ParseUint(s, 16, 64)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2017-11-20 22:19:32 +00:00
|
|
|
return strconv.FormatUint(id, 16), nil
|
2017-08-22 00:24:54 +00:00
|
|
|
}
|