1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2024-12-10 09:50:58 +02:00
opentelemetry-go/bridge/opentracing/bridge.go
Krzesimir Nowak 8cddf30cb2
Context propagation in opentracing bridge (#525)
* Propagate context changes in mix tests

We will need this for testing the correlation context and baggage
items propagation between the APIs.

* Add baggage interoperation tests

The test adds a baggage item to active OT span and some correlation
key value to current Otel span. Then makes sure that the OT span
contains both the baggage item and some translated version of the
correlation key value its Otel sibling got, and that the Otel span
contains both the correlation key value and the baggage item its OT
sibling got.

* Add hooks functionality to baggage propagation

This introduces two kinds of hooks into the correlation context
code.

The set hook gets called every time we set a Map in the context. The
hook receives a context with the Map and returns a new context.

The get hook gets called every time we get a Map from the context. The
hook receives the context and the map, and returns a new Map.

These hooks will be used for correlation context and baggage items
propagation between the Otel and OT APIs.

* Warn on foreign opentracing span

* fixup for using otel propagators

* Add utility function for setting up bridge and context

This prepares the context by installing the hooks, so the correlation
context and baggage items can be propagated between the APIs.

* Add bridge span constructor

So I do not need to remember about initializing a newly added member
in several places now.

* Propagate baggage across otel and OT APIs

This uses the set hook functionality to propagate correlation context
changes from Otel to OT spans by inserting keys and values into the
baggage items. The get hook functionality is used to propagate baggage
items from active OT span into the otel correlation context.

* Use correlation Map for baggage items

We will put this map into the context with correlation context
functions, and that is easier if we have correlation.Map, not
map[string]string.

* Use otel propagators in bridge

The otel propagators are now kinda sorta usable for opentracing
bridge. Some more work is needed to make it fully work, though -
correlation context set with the otel API is not propagated to OT
spans as baggage items yet.

Co-authored-by: Joshua MacDonald <jmacd@users.noreply.github.com>
2020-03-10 10:32:01 -07:00

653 lines
19 KiB
Go

// Copyright 2019, 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.
package opentracing
import (
"context"
"fmt"
"net/http"
"strings"
"sync"
"google.golang.org/grpc/codes"
ot "github.com/opentracing/opentracing-go"
otext "github.com/opentracing/opentracing-go/ext"
otlog "github.com/opentracing/opentracing-go/log"
otelcore "go.opentelemetry.io/otel/api/core"
otelcorrelation "go.opentelemetry.io/otel/api/correlation"
otelglobal "go.opentelemetry.io/otel/api/global"
otelkey "go.opentelemetry.io/otel/api/key"
otelpropagation "go.opentelemetry.io/otel/api/propagation"
oteltrace "go.opentelemetry.io/otel/api/trace"
otelparent "go.opentelemetry.io/otel/internal/trace/parent"
"go.opentelemetry.io/otel/bridge/opentracing/migration"
)
type bridgeSpanContext struct {
baggageItems otelcorrelation.Map
otelSpanContext otelcore.SpanContext
}
var _ ot.SpanContext = &bridgeSpanContext{}
func newBridgeSpanContext(otelSpanContext otelcore.SpanContext, parentOtSpanContext ot.SpanContext) *bridgeSpanContext {
bCtx := &bridgeSpanContext{
baggageItems: otelcorrelation.NewEmptyMap(),
otelSpanContext: otelSpanContext,
}
if parentOtSpanContext != nil {
parentOtSpanContext.ForeachBaggageItem(func(key, value string) bool {
bCtx.setBaggageItem(key, value)
return true
})
}
return bCtx
}
func (c *bridgeSpanContext) ForeachBaggageItem(handler func(k, v string) bool) {
c.baggageItems.Foreach(func(kv otelcore.KeyValue) bool {
return handler(string(kv.Key), kv.Value.Emit())
})
}
func (c *bridgeSpanContext) setBaggageItem(restrictedKey, value string) {
crk := http.CanonicalHeaderKey(restrictedKey)
c.baggageItems = c.baggageItems.Apply(otelcorrelation.MapUpdate{SingleKV: otelkey.New(crk).String(value)})
}
func (c *bridgeSpanContext) baggageItem(restrictedKey string) string {
crk := http.CanonicalHeaderKey(restrictedKey)
val, _ := c.baggageItems.Value(otelkey.New(crk))
return val.Emit()
}
type bridgeSpan struct {
otelSpan oteltrace.Span
ctx *bridgeSpanContext
tracer *BridgeTracer
skipDeferHook bool
extraBaggageItems map[string]string
}
var _ ot.Span = &bridgeSpan{}
func newBridgeSpan(otelSpan oteltrace.Span, bridgeSC *bridgeSpanContext, tracer *BridgeTracer) *bridgeSpan {
return &bridgeSpan{
otelSpan: otelSpan,
ctx: bridgeSC,
tracer: tracer,
skipDeferHook: false,
extraBaggageItems: nil,
}
}
func (s *bridgeSpan) Finish() {
s.otelSpan.End()
}
func (s *bridgeSpan) FinishWithOptions(opts ot.FinishOptions) {
var otelOpts []oteltrace.EndOption
if !opts.FinishTime.IsZero() {
otelOpts = append(otelOpts, oteltrace.WithEndTime(opts.FinishTime))
}
for _, record := range opts.LogRecords {
s.logRecord(record)
}
for _, data := range opts.BulkLogData {
s.logRecord(data.ToLogRecord())
}
s.otelSpan.End(otelOpts...)
}
func (s *bridgeSpan) logRecord(record ot.LogRecord) {
s.otelSpan.AddEventWithTimestamp(context.Background(), record.Timestamp, "", otLogFieldsToOtelCoreKeyValues(record.Fields)...)
}
func (s *bridgeSpan) Context() ot.SpanContext {
return s.ctx
}
func (s *bridgeSpan) SetOperationName(operationName string) ot.Span {
s.otelSpan.SetName(operationName)
return s
}
func (s *bridgeSpan) SetTag(key string, value interface{}) ot.Span {
switch key {
case string(otext.SpanKind):
// TODO: Should we ignore it?
case string(otext.Error):
if b, ok := value.(bool); ok && b {
s.otelSpan.SetStatus(codes.Unknown, "")
}
default:
s.otelSpan.SetAttributes(otTagToOtelCoreKeyValue(key, value))
}
return s
}
func (s *bridgeSpan) LogFields(fields ...otlog.Field) {
s.otelSpan.AddEvent(context.Background(), "", otLogFieldsToOtelCoreKeyValues(fields)...)
}
type bridgeFieldEncoder struct {
pairs []otelcore.KeyValue
}
var _ otlog.Encoder = &bridgeFieldEncoder{}
func (e *bridgeFieldEncoder) EmitString(key, value string) {
e.emitCommon(key, value)
}
func (e *bridgeFieldEncoder) EmitBool(key string, value bool) {
e.emitCommon(key, value)
}
func (e *bridgeFieldEncoder) EmitInt(key string, value int) {
e.emitCommon(key, value)
}
func (e *bridgeFieldEncoder) EmitInt32(key string, value int32) {
e.emitCommon(key, value)
}
func (e *bridgeFieldEncoder) EmitInt64(key string, value int64) {
e.emitCommon(key, value)
}
func (e *bridgeFieldEncoder) EmitUint32(key string, value uint32) {
e.emitCommon(key, value)
}
func (e *bridgeFieldEncoder) EmitUint64(key string, value uint64) {
e.emitCommon(key, value)
}
func (e *bridgeFieldEncoder) EmitFloat32(key string, value float32) {
e.emitCommon(key, value)
}
func (e *bridgeFieldEncoder) EmitFloat64(key string, value float64) {
e.emitCommon(key, value)
}
func (e *bridgeFieldEncoder) EmitObject(key string, value interface{}) {
e.emitCommon(key, value)
}
func (e *bridgeFieldEncoder) EmitLazyLogger(value otlog.LazyLogger) {
value(e)
}
func (e *bridgeFieldEncoder) emitCommon(key string, value interface{}) {
e.pairs = append(e.pairs, otTagToOtelCoreKeyValue(key, value))
}
func otLogFieldsToOtelCoreKeyValues(fields []otlog.Field) []otelcore.KeyValue {
encoder := &bridgeFieldEncoder{}
for _, field := range fields {
field.Marshal(encoder)
}
return encoder.pairs
}
func (s *bridgeSpan) LogKV(alternatingKeyValues ...interface{}) {
fields, err := otlog.InterleavedKVToFields(alternatingKeyValues...)
if err != nil {
return
}
s.LogFields(fields...)
}
func (s *bridgeSpan) SetBaggageItem(restrictedKey, value string) ot.Span {
s.updateOtelContext(restrictedKey, value)
s.setBaggageItemOnly(restrictedKey, value)
return s
}
func (s *bridgeSpan) setBaggageItemOnly(restrictedKey, value string) {
s.ctx.setBaggageItem(restrictedKey, value)
}
func (s *bridgeSpan) updateOtelContext(restrictedKey, value string) {
if s.extraBaggageItems == nil {
s.extraBaggageItems = make(map[string]string)
}
s.extraBaggageItems[restrictedKey] = value
}
func (s *bridgeSpan) BaggageItem(restrictedKey string) string {
return s.ctx.baggageItem(restrictedKey)
}
func (s *bridgeSpan) Tracer() ot.Tracer {
return s.tracer
}
func (s *bridgeSpan) LogEvent(event string) {
s.LogEventWithPayload(event, nil)
}
func (s *bridgeSpan) LogEventWithPayload(event string, payload interface{}) {
data := ot.LogData{
Event: event,
Payload: payload,
}
s.Log(data)
}
func (s *bridgeSpan) Log(data ot.LogData) {
record := data.ToLogRecord()
s.LogFields(record.Fields...)
}
type bridgeSetTracer struct {
isSet bool
otelTracer oteltrace.Tracer
warningHandler BridgeWarningHandler
warnOnce sync.Once
}
func (s *bridgeSetTracer) tracer() oteltrace.Tracer {
if !s.isSet {
s.warnOnce.Do(func() {
s.warningHandler("The OpenTelemetry tracer is not set, default no-op tracer is used! Call SetOpenTelemetryTracer to set it up.\n")
})
}
return s.otelTracer
}
// BridgeWarningHandler is a type of handler that receives warnings
// from the BridgeTracer.
type BridgeWarningHandler func(msg string)
// BridgeTracer is an implementation of the OpenTracing tracer, which
// translates the calls to the OpenTracing API into OpenTelemetry
// counterparts and calls the underlying OpenTelemetry tracer.
type BridgeTracer struct {
setTracer bridgeSetTracer
warningHandler BridgeWarningHandler
warnOnce sync.Once
propagators otelpropagation.Propagators
}
var _ ot.Tracer = &BridgeTracer{}
var _ ot.TracerContextWithSpanExtension = &BridgeTracer{}
// NewBridgeTracer creates a new BridgeTracer. The new tracer forwards
// the calls to the OpenTelemetry Noop tracer, so it should be
// overridden with the SetOpenTelemetryTracer function. The warnings
// handler does nothing by default, so to override it use the
// SetWarningHandler function.
func NewBridgeTracer() *BridgeTracer {
return &BridgeTracer{
setTracer: bridgeSetTracer{
otelTracer: oteltrace.NoopTracer{},
},
warningHandler: func(msg string) {},
propagators: nil,
}
}
// SetWarningHandler overrides the warning handler.
func (t *BridgeTracer) SetWarningHandler(handler BridgeWarningHandler) {
t.setTracer.warningHandler = handler
t.warningHandler = handler
}
// SetWarningHandler overrides the underlying OpenTelemetry
// tracer. The passed tracer should know how to operate in the
// environment that uses OpenTracing API.
func (t *BridgeTracer) SetOpenTelemetryTracer(tracer oteltrace.Tracer) {
t.setTracer.otelTracer = tracer
t.setTracer.isSet = true
}
func (t *BridgeTracer) SetPropagators(propagators otelpropagation.Propagators) {
t.propagators = propagators
}
func (t *BridgeTracer) NewHookedContext(ctx context.Context) context.Context {
ctx = otelcorrelation.ContextWithSetHook(ctx, t.correlationSetHook)
ctx = otelcorrelation.ContextWithGetHook(ctx, t.correlationGetHook)
return ctx
}
func (t *BridgeTracer) correlationSetHook(ctx context.Context) context.Context {
span := ot.SpanFromContext(ctx)
if span == nil {
t.warningHandler("No active OpenTracing span, can not propagate the baggage items from OpenTelemetry context\n")
return ctx
}
bSpan, ok := span.(*bridgeSpan)
if !ok {
t.warningHandler("Encountered a foreign OpenTracing span, will not propagate the baggage items from OpenTelemetry context\n")
return ctx
}
// we clear the context only to avoid calling a get hook
// during MapFromContext, but otherwise we don't change the
// context, so we don't care about the old hooks.
clearCtx, _, _ := otelcorrelation.ContextWithNoHooks(ctx)
m := otelcorrelation.MapFromContext(clearCtx)
m.Foreach(func(kv otelcore.KeyValue) bool {
bSpan.setBaggageItemOnly(string(kv.Key), kv.Value.Emit())
return true
})
return ctx
}
func (t *BridgeTracer) correlationGetHook(ctx context.Context, m otelcorrelation.Map) otelcorrelation.Map {
span := ot.SpanFromContext(ctx)
if span == nil {
t.warningHandler("No active OpenTracing span, can not propagate the baggage items from OpenTracing span context\n")
return m
}
bSpan, ok := span.(*bridgeSpan)
if !ok {
t.warningHandler("Encountered a foreign OpenTracing span, will not propagate the baggage items from OpenTracing span context\n")
return m
}
items := bSpan.extraBaggageItems
if len(items) == 0 {
return m
}
kv := make([]otelcore.KeyValue, 0, len(items))
for k, v := range items {
kv = append(kv, otelkey.String(k, v))
}
return m.Apply(otelcorrelation.MapUpdate{MultiKV: kv})
}
// StartSpan is a part of the implementation of the OpenTracing Tracer
// interface.
func (t *BridgeTracer) StartSpan(operationName string, opts ...ot.StartSpanOption) ot.Span {
sso := ot.StartSpanOptions{}
for _, opt := range opts {
opt.Apply(&sso)
}
parentBridgeSC, links := otSpanReferencesToParentAndLinks(sso.References)
attributes, kind, hadTrueErrorTag := otTagsToOtelAttributesKindAndError(sso.Tags)
checkCtx := migration.WithDeferredSetup(context.Background())
if parentBridgeSC != nil {
checkCtx = oteltrace.ContextWithRemoteSpanContext(checkCtx, parentBridgeSC.otelSpanContext)
}
checkCtx2, otelSpan := t.setTracer.tracer().Start(checkCtx, operationName, func(opts *oteltrace.StartConfig) {
opts.Attributes = attributes
opts.StartTime = sso.StartTime
opts.Links = links
opts.Record = true
opts.NewRoot = false
opts.SpanKind = kind
})
if checkCtx != checkCtx2 {
t.warnOnce.Do(func() {
t.warningHandler("SDK should have deferred the context setup, see the documentation of go.opentelemetry.io/otel/bridge/opentracing/migration\n")
})
}
if hadTrueErrorTag {
otelSpan.SetStatus(codes.Unknown, "")
}
// One does not simply pass a concrete pointer to function
// that takes some interface. In case of passing nil concrete
// pointer, we get an interface with non-nil type (because the
// pointer type is known) and a nil value. Which means
// interface is not nil, but calling some interface function
// on it will most likely result in nil pointer dereference.
var otSpanContext ot.SpanContext
if parentBridgeSC != nil {
otSpanContext = parentBridgeSC
}
sctx := newBridgeSpanContext(otelSpan.SpanContext(), otSpanContext)
span := newBridgeSpan(otelSpan, sctx, t)
return span
}
// ContextWithBridgeSpan sets up the context with the passed
// OpenTelemetry span as the active OpenTracing span.
//
// This function should be used by the OpenTelemetry tracers that want
// to be aware how to operate in the environment using OpenTracing
// API.
func (t *BridgeTracer) ContextWithBridgeSpan(ctx context.Context, span oteltrace.Span) context.Context {
var otSpanContext ot.SpanContext
if parentSpan := ot.SpanFromContext(ctx); parentSpan != nil {
otSpanContext = parentSpan.Context()
}
bCtx := newBridgeSpanContext(span.SpanContext(), otSpanContext)
bSpan := newBridgeSpan(span, bCtx, t)
bSpan.skipDeferHook = true
return ot.ContextWithSpan(ctx, bSpan)
}
// ContextWithSpanHook is an implementation of the OpenTracing tracer
// extension interface. It will call the DeferredContextSetupHook
// function on the tracer if it implements the
// DeferredContextSetupTracerExtension interface.
func (t *BridgeTracer) ContextWithSpanHook(ctx context.Context, span ot.Span) context.Context {
bSpan, ok := span.(*bridgeSpan)
if !ok {
t.warningHandler("Encountered a foreign OpenTracing span, will not run a possible deferred context setup hook\n")
return ctx
}
if bSpan.skipDeferHook {
return ctx
}
if tracerWithExtension, ok := bSpan.tracer.setTracer.tracer().(migration.DeferredContextSetupTracerExtension); ok {
ctx = tracerWithExtension.DeferredContextSetupHook(ctx, bSpan.otelSpan)
}
return ctx
}
func otTagsToOtelAttributesKindAndError(tags map[string]interface{}) ([]otelcore.KeyValue, oteltrace.SpanKind, bool) {
kind := oteltrace.SpanKindInternal
err := false
var pairs []otelcore.KeyValue
for k, v := range tags {
switch k {
case string(otext.SpanKind):
if s, ok := v.(string); ok {
switch strings.ToLower(s) {
case "client":
kind = oteltrace.SpanKindClient
case "server":
kind = oteltrace.SpanKindServer
case "producer":
kind = oteltrace.SpanKindProducer
case "consumer":
kind = oteltrace.SpanKindConsumer
}
}
case string(otext.Error):
if b, ok := v.(bool); ok && b {
err = true
}
default:
pairs = append(pairs, otTagToOtelCoreKeyValue(k, v))
}
}
return pairs, kind, err
}
func otTagToOtelCoreKeyValue(k string, v interface{}) otelcore.KeyValue {
key := otTagToOtelCoreKey(k)
switch val := v.(type) {
case bool:
return key.Bool(val)
case int64:
return key.Int64(val)
case uint64:
return key.Uint64(val)
case float64:
return key.Float64(val)
case int32:
return key.Int32(val)
case uint32:
return key.Uint32(val)
case float32:
return key.Float32(val)
case int:
return key.Int(val)
case uint:
return key.Uint(val)
case string:
return key.String(val)
default:
return key.String(fmt.Sprint(v))
}
}
func otTagToOtelCoreKey(k string) otelcore.Key {
return otelcore.Key(k)
}
func otSpanReferencesToParentAndLinks(references []ot.SpanReference) (*bridgeSpanContext, []oteltrace.Link) {
var (
parent *bridgeSpanContext
links []oteltrace.Link
)
for _, reference := range references {
bridgeSC, ok := reference.ReferencedContext.(*bridgeSpanContext)
if !ok {
// We ignore foreign ot span contexts,
// sorry. We have no way of getting any
// TraceID and SpanID out of it for form a
// otelcore.SpanContext for otelcore.Link. And
// we can't make it a parent - it also needs a
// valid otelcore.SpanContext.
continue
}
if parent != nil {
links = append(links, otSpanReferenceToOtelLink(bridgeSC, reference.Type))
} else {
if reference.Type == ot.ChildOfRef {
parent = bridgeSC
} else {
links = append(links, otSpanReferenceToOtelLink(bridgeSC, reference.Type))
}
}
}
return parent, links
}
func otSpanReferenceToOtelLink(bridgeSC *bridgeSpanContext, refType ot.SpanReferenceType) oteltrace.Link {
return oteltrace.Link{
SpanContext: bridgeSC.otelSpanContext,
Attributes: otSpanReferenceTypeToOtelLinkAttributes(refType),
}
}
func otSpanReferenceTypeToOtelLinkAttributes(refType ot.SpanReferenceType) []otelcore.KeyValue {
return []otelcore.KeyValue{
otelkey.String("ot-span-reference-type", otSpanReferenceTypeToString(refType)),
}
}
func otSpanReferenceTypeToString(refType ot.SpanReferenceType) string {
switch refType {
case ot.ChildOfRef:
// "extra", because first child-of reference is used
// as a parent, so this function isn't even called for
// it.
return "extra-child-of"
case ot.FollowsFromRef:
return "follows-from-ref"
default:
return fmt.Sprintf("unknown-%d", int(refType))
}
}
// fakeSpan is just a holder of span context, nothing more. It's for
// propagators, so they can get the span context from Go context.
type fakeSpan struct {
oteltrace.NoopSpan
sc otelcore.SpanContext
}
func (s fakeSpan) SpanContext() otelcore.SpanContext {
return s.sc
}
// Inject is a part of the implementation of the OpenTracing Tracer
// interface.
//
// Currently only the HTTPHeaders format is supported.
func (t *BridgeTracer) Inject(sm ot.SpanContext, format interface{}, carrier interface{}) error {
bridgeSC, ok := sm.(*bridgeSpanContext)
if !ok {
return ot.ErrInvalidSpanContext
}
if !bridgeSC.otelSpanContext.IsValid() {
return ot.ErrInvalidSpanContext
}
if builtinFormat, ok := format.(ot.BuiltinFormat); !ok || builtinFormat != ot.HTTPHeaders {
return ot.ErrUnsupportedFormat
}
hhcarrier, ok := carrier.(ot.HTTPHeadersCarrier)
if !ok {
return ot.ErrInvalidCarrier
}
header := http.Header(hhcarrier)
fs := fakeSpan{
sc: bridgeSC.otelSpanContext,
}
ctx := oteltrace.ContextWithSpan(context.Background(), fs)
ctx = otelcorrelation.ContextWithMap(ctx, bridgeSC.baggageItems)
otelpropagation.InjectHTTP(ctx, t.getPropagators(), header)
return nil
}
// Extract is a part of the implementation of the OpenTracing Tracer
// interface.
//
// Currently only the HTTPHeaders format is supported.
func (t *BridgeTracer) Extract(format interface{}, carrier interface{}) (ot.SpanContext, error) {
if builtinFormat, ok := format.(ot.BuiltinFormat); !ok || builtinFormat != ot.HTTPHeaders {
return nil, ot.ErrUnsupportedFormat
}
hhcarrier, ok := carrier.(ot.HTTPHeadersCarrier)
if !ok {
return nil, ot.ErrInvalidCarrier
}
header := http.Header(hhcarrier)
ctx := otelpropagation.ExtractHTTP(context.Background(), t.getPropagators(), header)
baggage := otelcorrelation.MapFromContext(ctx)
otelSC, _, _ := otelparent.GetSpanContextAndLinks(ctx, false)
bridgeSC := &bridgeSpanContext{
baggageItems: baggage,
otelSpanContext: otelSC,
}
if !bridgeSC.otelSpanContext.IsValid() {
return nil, ot.ErrSpanContextNotFound
}
return bridgeSC, nil
}
func (t *BridgeTracer) getPropagators() otelpropagation.Propagators {
if t.propagators != nil {
return t.propagators
}
return otelglobal.Propagators()
}