1package lightstepoc
2
3import (
4	"context"
5	"errors"
6
7	"github.com/lightstep/lightstep-tracer-go"
8	"github.com/lightstep/lightstep-tracer-go/lightstepoc/internal/conversions"
9	"github.com/opentracing/opentracing-go"
10	"github.com/opentracing/opentracing-go/ext"
11	"github.com/opentracing/opentracing-go/log"
12	"go.opencensus.io/trace"
13)
14
15var (
16	// ErrFailedToCreateExporter indicates that the underlying tracer could not be created,
17	// but the root reason is not known.
18	ErrFailedToCreateExporter = errors.New("lightstepoc: failed to create exporter")
19)
20
21// Exporter may be registered with OpenCensus so that span data can be exported to LightStep
22type Exporter struct {
23	tracer lightstep.Tracer
24}
25
26// NewExporter creates a new Exporter.
27// It returns an error if the underlying tracer could not be created, e.g., due to invalid options
28func NewExporter(opts ...Option) (*Exporter, error) {
29	c := defaultConfig()
30
31	for _, opt := range opts {
32		opt(c)
33	}
34
35	tracer := lightstep.NewTracer(c.tracerOptions)
36	if tracer == nil {
37		if err := c.tracerOptions.Validate(); err != nil {
38			return nil, err
39		}
40
41		return nil, ErrFailedToCreateExporter
42	}
43
44	return &Exporter{
45		tracer: tracer,
46	}, nil
47}
48
49// ExportSpan queues the span to be sent LightStep.
50// Spans are typically batched for performance reasons. Call `Exporter#Flush` to send all queued spans.
51func (e *Exporter) ExportSpan(sd *trace.SpanData) {
52	opts := []opentracing.StartSpanOption{
53		opentracing.StartTime(sd.StartTime),
54		lightstep.SetTraceID(conversions.ConvertTraceID(sd.SpanContext.TraceID)),
55		lightstep.SetSpanID(conversions.ConvertSpanID(sd.SpanContext.SpanID)),
56		lightstep.SetParentSpanID(conversions.ConvertSpanID(sd.ParentSpanID)),
57	}
58
59	for _, link := range sd.Links {
60		if link.Type == trace.LinkTypeChild {
61			spanContext := conversions.ConvertLinkToSpanContext(link)
62			opts = append(opts, opentracing.ChildOf(spanContext))
63		}
64	}
65
66	switch sd.SpanKind {
67	case trace.SpanKindServer:
68		opts = append(opts, ext.SpanKindRPCServer)
69	case trace.SpanKindClient:
70		opts = append(opts, ext.SpanKindRPCClient)
71	}
72
73	span := e.tracer.StartSpan(sd.Name, opts...)
74
75	for _, entry := range sd.SpanContext.Tracestate.Entries() {
76		span.SetBaggageItem(entry.Key, entry.Value)
77	}
78
79	ext.HTTPStatusCode.Set(span, uint16(sd.Status.Code))
80
81	for k, v := range sd.Attributes {
82		span.SetTag(k, v)
83	}
84
85	var logRecords []opentracing.LogRecord
86	for _, annotation := range sd.Annotations {
87		logRecords = append(logRecords, opentracing.LogRecord{
88			Timestamp: annotation.Time,
89			Fields:    []log.Field{log.Object(annotation.Message, annotation.Attributes)},
90		})
91	}
92
93	span.FinishWithOptions(opentracing.FinishOptions{
94		FinishTime: sd.EndTime,
95		LogRecords: logRecords,
96	})
97}
98
99// Flush sends all buffered spans to LightStep
100func (e *Exporter) Flush(ctx context.Context) {
101	e.tracer.Flush(ctx)
102}
103
104// Close flushes all buffered spans and then kills open connections to LightStep, releasing resources
105func (e *Exporter) Close(ctx context.Context) {
106	e.tracer.Close(ctx)
107}
108