diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..8e9f6d2 --- /dev/null +++ b/go.mod @@ -0,0 +1,14 @@ +module go.unistack.org/micro-tracer-instana/v4 + +go 1.20 + +require ( + github.com/instana/go-sensor v1.55.2 + github.com/opentracing/opentracing-go v1.2.0 + go.unistack.org/micro/v4 v4.0.7 +) + +require ( + github.com/looplab/fsm v1.0.1 // indirect + github.com/stretchr/testify v1.8.3 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..015c1b4 --- /dev/null +++ b/go.sum @@ -0,0 +1,26 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/instana/go-sensor v1.55.2 h1:XdLN38mSFsHpaL+jIDkE/ZrW7pxgPeUC/bV9bSwVHyM= +github.com/instana/go-sensor v1.55.2/go.mod h1:Ks06EG9Da5O3hbdJiHIePG/vNmToovkaJjMlUBd70Yc= +github.com/looplab/fsm v1.0.1 h1:OEW0ORrIx095N/6lgoGkFkotqH6s7vaFPsgjLAaF5QU= +github.com/looplab/fsm v1.0.1/go.mod h1:PmD3fFvQEIsjMEfvZdrCDZ6y8VwKTwWNjlpEr6IKPO4= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +go.unistack.org/micro/v4 v4.0.7 h1:2lwtZlHcSwgkahhFbkI4x1lOS79lw8uLHtcEhlFF+AM= +go.unistack.org/micro/v4 v4.0.7/go.mod h1:bVEYTlPi0EsdgZZt311bIroDg9ict7ky3C87dSCCAGk= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/instana.go b/instana.go new file mode 100644 index 0000000..3223496 --- /dev/null +++ b/instana.go @@ -0,0 +1,191 @@ +package instana + +import ( + "context" + + sensor "github.com/instana/go-sensor" + "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go/log" + "go.unistack.org/micro/v4/metadata" + "go.unistack.org/micro/v4/options" + "go.unistack.org/micro/v4/tracer" +) + +var _ tracer.Tracer = &Tracer{} + +type Tracer struct { + opts tracer.Options + sensor sensor.Tracer +} + +func (ot *Tracer) Name() string { + return ot.opts.Name +} + +func (ot *Tracer) Flush(ctx context.Context) error { + return nil +} + +func (ot *Tracer) Init(opts ...options.Option) error { + for _, o := range opts { + o(&ot.opts) + } + + sensorOptions := sensor.DefaultOptions() + if v, ok := ot.opts.Context.Value(tracerOptionsKey{}).(*sensor.Options); ok && v != nil { + sensorOptions = v + } + + ot.sensor = sensor.NewSensorWithTracer(sensor.NewTracerWithOptions(sensorOptions)) + + return nil +} + +func (ot *Tracer) Start(ctx context.Context, name string, opts ...options.Option) (context.Context, tracer.Span) { + options := tracer.NewSpanOptions(opts...) + var span opentracing.Span + switch options.Kind { + case tracer.SpanKindInternal, tracer.SpanKindUnspecified: + ctx, span = ot.startSpanFromContext(ctx, name) + case tracer.SpanKindClient, tracer.SpanKindProducer: + ctx, span = ot.startSpanFromOutgoingContext(ctx, name) + case tracer.SpanKindServer, tracer.SpanKindConsumer: + ctx, span = ot.startSpanFromIncomingContext(ctx, name) + } + return ctx, &otSpan{span: span, opts: options} +} + +type otSpan struct { + span opentracing.Span + sensor sensor.Tracer + topts tracer.Options + opts tracer.SpanOptions + status tracer.SpanStatus + statusMsg string +} + +func (os *otSpan) SetStatus(st tracer.SpanStatus, msg string) { + switch st { + case tracer.SpanStatusError: + os.span.SetTag("error", true) + } + os.status = st + os.statusMsg = msg +} + +func (os *otSpan) Status() (tracer.SpanStatus, string) { + return os.status, os.statusMsg +} + +func (os *otSpan) Tracer() tracer.Tracer { + return &Tracer{sensor: os.sensor, opts: os.topts} +} + +func (os *otSpan) Finish(opts ...options.Option) { + if len(os.opts.Labels) > 0 { + os.span.LogKV(os.opts.Labels...) + } + os.span.Finish() +} + +func (os *otSpan) AddEvent(name string, opts ...options.Option) { + os.span.LogFields(log.Event(name)) +} + +func (os *otSpan) Context() context.Context { + return opentracing.ContextWithSpan(context.Background(), os.span) +} + +func (os *otSpan) SetName(name string) { + os.span = os.span.SetOperationName(name) +} + +func (os *otSpan) SetLabels(labels ...interface{}) { + os.opts.Labels = labels +} + +func (os *otSpan) Kind() tracer.SpanKind { + return os.opts.Kind +} + +func (os *otSpan) AddLabels(labels ...interface{}) { + os.opts.Labels = append(os.opts.Labels, labels...) +} + +func NewTracer(opts ...options.Option) *Tracer { + options := tracer.NewOptions(opts...) + return &Tracer{opts: options} +} + +func spanFromContext(ctx context.Context) (opentracing.Span, bool) { + return sensor.SpanFromContext(ctx) +} + +func (ot *Tracer) startSpanFromContext(ctx context.Context, name string, opts ...opentracing.StartSpanOption) (context.Context, opentracing.Span) { + if parentSpan := opentracing.SpanFromContext(ctx); parentSpan != nil { + opts = append(opts, opentracing.ChildOf(parentSpan.Context())) + } + + md := metadata.New(1) + + sp := ot.sensor.StartSpan(name, opts...) + if err := sp.Tracer().Inject(sp.Context(), opentracing.TextMap, opentracing.TextMapCarrier(md)); err != nil { + return nil, nil + } + + ctx = opentracing.ContextWithSpan(ctx, sp) + + return ctx, sp +} + +func (ot *Tracer) startSpanFromOutgoingContext(ctx context.Context, name string, opts ...opentracing.StartSpanOption) (context.Context, opentracing.Span) { + var parentCtx opentracing.SpanContext + + md, ok := metadata.FromOutgoingContext(ctx) + if ok && md != nil { + if spanCtx, err := ot.sensor.Extract(opentracing.TextMap, opentracing.TextMapCarrier(md)); err == nil && ok { + parentCtx = spanCtx + } + } + + if parentCtx != nil { + opts = append(opts, opentracing.ChildOf(parentCtx)) + } + + nmd := metadata.Copy(md) + + sp := ot.sensor.StartSpan(name, opts...) + if err := sp.Tracer().Inject(sp.Context(), opentracing.TextMap, opentracing.TextMapCarrier(nmd)); err != nil { + return nil, nil + } + + ctx = metadata.NewOutgoingContext(opentracing.ContextWithSpan(ctx, sp), nmd) + + return ctx, sp +} + +func (ot *Tracer) startSpanFromIncomingContext(ctx context.Context, name string, opts ...opentracing.StartSpanOption) (context.Context, opentracing.Span) { + var parentCtx opentracing.SpanContext + + md, ok := metadata.FromIncomingContext(ctx) + if ok && md != nil { + if spanCtx, err := ot.sensor.Extract(opentracing.TextMap, opentracing.TextMapCarrier(md)); err == nil { + parentCtx = spanCtx + } + } + + if parentCtx != nil { + opts = append(opts, opentracing.ChildOf(parentCtx)) + } + + nmd := metadata.Copy(md) + + sp := ot.sensor.StartSpan(name, opts...) + if err := sp.Tracer().Inject(sp.Context(), opentracing.TextMap, opentracing.TextMapCarrier(nmd)); err != nil { + return nil, nil + } + + ctx = metadata.NewIncomingContext(opentracing.ContextWithSpan(ctx, sp), nmd) + + return ctx, sp +} diff --git a/options.go b/options.go new file mode 100644 index 0000000..684e759 --- /dev/null +++ b/options.go @@ -0,0 +1,12 @@ +package instana + +import ( + sensor "github.com/instana/go-sensor" + "go.unistack.org/micro/v4/options" +) + +type tracerOptionsKey struct{} + +func Options(opts *sensor.Options) options.Option { + return options.ContextOption(tracerOptionsKey{}, opts) +}