gitlab.com/gitlab-org/labkit@v1.21.0/tracing/impl/stackdriver_tracer.go (about)

     1  // +build tracer_static,tracer_static_stackdriver
     2  
     3  package impl
     4  
     5  import (
     6  	"context"
     7  	"encoding/base64"
     8  	"fmt"
     9  	"io"
    10  	"reflect"
    11  	"strconv"
    12  	"time"
    13  
    14  	"contrib.go.opencensus.io/exporter/stackdriver"
    15  	opentracing "github.com/opentracing/opentracing-go"
    16  	opentracinglog "github.com/opentracing/opentracing-go/log"
    17  	log "github.com/sirupsen/logrus"
    18  	"go.opencensus.io/trace"
    19  	"go.opencensus.io/trace/propagation"
    20  )
    21  
    22  // to play nice with grpc_opentracing tagsCarrier
    23  // https://github.com/grpc-ecosystem/go-grpc-middleware/blob/a77ba4df9c270ec918ed6a6d506309078e3e4c4d/tracing/opentracing/id_extract.go#L30
    24  // https://github.com/grpc-ecosystem/go-grpc-middleware/blob/a77ba4df9c270ec918ed6a6d506309078e3e4c4d/tracing/opentracing/options.go#L48
    25  var traceHeader = "uber-trace-id"
    26  
    27  // https://pkg.go.dev/github.com/opentracing/opentracing-go#Tracer
    28  type adapterTracer struct {
    29  	exporter *stackdriver.Exporter
    30  }
    31  
    32  // opencensus does not support overwriting StartTime; so we only implement Tags
    33  // https://pkg.go.dev/github.com/opentracing/opentracing-go#StartSpanOption
    34  func (tracer *adapterTracer) StartSpan(operationName string, opts ...opentracing.StartSpanOption) opentracing.Span {
    35  	sso := opentracing.StartSpanOptions{}
    36  	for _, o := range opts {
    37  		o.Apply(&sso)
    38  	}
    39  
    40  	var ctx context.Context
    41  	var adapterSpanCtx *adapterSpanContext
    42  	ctx = context.Background()
    43  
    44  	for _, ref := range sso.References {
    45  		if ref.Type == opentracing.ChildOfRef {
    46  			if v, ok := ref.ReferencedContext.(adapterSpanContext); ok {
    47  				ctx = v.ctx
    48  				adapterSpanCtx = &v
    49  				break
    50  			}
    51  		}
    52  	}
    53  
    54  	var span *trace.Span
    55  	if adapterSpanCtx != nil && adapterSpanCtx.remote {
    56  		ctx, span = trace.StartSpanWithRemoteParent(ctx, operationName, *adapterSpanCtx.ocSpanCtx)
    57  	} else {
    58  		ctx, span = trace.StartSpan(ctx, operationName)
    59  	}
    60  
    61  	spanContext := span.SpanContext()
    62  	adapterSpan := &adapterSpan{span, tracer, adapterSpanContext{ctx, false, &spanContext}}
    63  	for k, v := range sso.Tags {
    64  		adapterSpan.SetTag(k, v)
    65  	}
    66  	return adapterSpan
    67  }
    68  
    69  func (tracer *adapterTracer) Inject(sm opentracing.SpanContext, format interface{}, carrier interface{}) error {
    70  	c, ok := sm.(adapterSpanContext)
    71  	if !ok {
    72  		return opentracing.ErrInvalidSpanContext
    73  	}
    74  	if format != opentracing.TextMap && format != opentracing.HTTPHeaders {
    75  		return opentracing.ErrUnsupportedFormat
    76  	}
    77  
    78  	ocSpanCtx := trace.FromContext(c.ctx).SpanContext()
    79  	encoded := base64.StdEncoding.EncodeToString(propagation.Binary(ocSpanCtx))
    80  	carrier.(opentracing.TextMapWriter).Set(traceHeader, string(encoded))
    81  
    82  	return nil
    83  }
    84  
    85  func (tracer *adapterTracer) Extract(format interface{}, carrier interface{}) (opentracing.SpanContext, error) {
    86  	if format != opentracing.TextMap && format != opentracing.HTTPHeaders {
    87  		return nil, opentracing.ErrUnsupportedFormat
    88  	}
    89  
    90  	var ocSpanCtx trace.SpanContext
    91  
    92  	err := carrier.(opentracing.TextMapReader).ForeachKey(func(key, val string) error {
    93  		var ok bool
    94  		if key == traceHeader {
    95  			decoded, err := base64.StdEncoding.DecodeString(val)
    96  			if err != nil {
    97  				return err
    98  			}
    99  			ocSpanCtx, ok = propagation.FromBinary(decoded)
   100  			if !ok {
   101  				return opentracing.ErrInvalidCarrier
   102  			}
   103  		}
   104  		return nil
   105  	})
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  
   110  	return adapterSpanContext{
   111  		ctx:       context.Background(),
   112  		remote:    true,
   113  		ocSpanCtx: &ocSpanCtx,
   114  	}, nil
   115  }
   116  
   117  type adapterSpanContext struct {
   118  	ctx       context.Context
   119  	remote    bool
   120  	ocSpanCtx *trace.SpanContext
   121  }
   122  
   123  func (c adapterSpanContext) ForeachBaggageItem(handler func(k, v string) bool) {}
   124  func (c adapterSpanContext) IsSampled() bool {
   125  	return c.ocSpanCtx.IsSampled()
   126  }
   127  
   128  // https://pkg.go.dev/go.opencensus.io/trace#Span
   129  // https://pkg.go.dev/github.com/opentracing/opentracing-go#Span
   130  type adapterSpan struct {
   131  	span        trace.SpanInterface
   132  	tracer      opentracing.Tracer
   133  	spanContext adapterSpanContext
   134  }
   135  
   136  func (span *adapterSpan) Finish() {
   137  	span.span.End()
   138  }
   139  
   140  func (span *adapterSpan) FinishWithOptions(opts opentracing.FinishOptions) {
   141  	span.span.End()
   142  }
   143  
   144  func (span *adapterSpan) Context() opentracing.SpanContext {
   145  	return span.spanContext
   146  }
   147  
   148  func (span *adapterSpan) SetOperationName(operationName string) opentracing.Span {
   149  	span.span.SetName(operationName)
   150  	return span
   151  }
   152  
   153  func castToAttribute(key string, value interface{}) []trace.Attribute {
   154  	switch v := reflect.ValueOf(value); v.Kind() {
   155  	case reflect.Bool:
   156  		return []trace.Attribute{trace.BoolAttribute(key, v.Bool())}
   157  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   158  		return []trace.Attribute{trace.Int64Attribute(key, v.Int())}
   159  	case reflect.Float32, reflect.Float64:
   160  		return []trace.Attribute{trace.Float64Attribute(key, v.Float())}
   161  	case reflect.String:
   162  		chunks := chunkString(v.String(), 256)
   163  		attrs := []trace.Attribute{}
   164  		for i, chunk := range chunks {
   165  			var k string
   166  			if i == 0 {
   167  				k = key
   168  			} else {
   169  				k = key + "." + strconv.Itoa(i)
   170  			}
   171  			attrs = append(attrs, trace.StringAttribute(k, chunk))
   172  		}
   173  		return attrs
   174  	default:
   175  		return []trace.Attribute{trace.StringAttribute(key, fmt.Sprintf("castToAttribute not implemented for type %+v", v.Kind()))}
   176  	}
   177  }
   178  
   179  // stackdriver limits attribute values to 256 bytes
   180  // we use runes here, hopefully that's close enough
   181  // values get truncated silently, so we can set $key to the full value
   182  // and then $key.1, $key.2, $key.3 are explicitly sliced.
   183  func chunkString(s string, chunkSize int) []string {
   184  	if len(s) == 0 {
   185  		return []string{s}
   186  	}
   187  	var strs []string
   188  	for i := 0; i*chunkSize < len(s); i++ {
   189  		end := (i + 1) * chunkSize
   190  		if end > len(s) {
   191  			end = len(s)
   192  		}
   193  		strs = append(strs, s[i*chunkSize:end])
   194  	}
   195  	return strs
   196  }
   197  
   198  func (span *adapterSpan) SetTag(key string, value interface{}) opentracing.Span {
   199  	span.span.AddAttributes(castToAttribute(key, value)...)
   200  	return span
   201  }
   202  
   203  func (span *adapterSpan) LogFields(fields ...opentracinglog.Field) {
   204  	eventName := ""
   205  	attributes := []trace.Attribute{}
   206  	for _, field := range fields {
   207  		if field.Key() == "event" {
   208  			eventName = field.String()
   209  			continue
   210  		}
   211  		attributes = append(attributes, castToAttribute(field.Key(), field.Value())...)
   212  	}
   213  	span.span.Annotate(attributes, eventName)
   214  }
   215  
   216  func (span *adapterSpan) LogKV(alternatingKeyValues ...interface{}) {
   217  	if (len(alternatingKeyValues) % 2) != 0 {
   218  		log.Print("stackdriver tracer: warning: even number of arguments required to LogKV")
   219  	}
   220  
   221  	attributes := []trace.Attribute{}
   222  	eventName := ""
   223  
   224  	for i := 0; i < len(alternatingKeyValues); i += 2 {
   225  		key := alternatingKeyValues[i].(string)
   226  		value := alternatingKeyValues[i+1]
   227  		if key == "event" {
   228  			eventName = value.(string)
   229  			continue
   230  		}
   231  		attributes = append(attributes, castToAttribute(key, value)...)
   232  	}
   233  
   234  	span.span.Annotate(attributes, eventName)
   235  }
   236  
   237  func (span *adapterSpan) SetBaggageItem(restrictedKey, value string) opentracing.Span {
   238  	return span
   239  }
   240  
   241  func (span *adapterSpan) BaggageItem(restrictedKey string) string {
   242  	// not implemented
   243  	return ""
   244  }
   245  
   246  func (span *adapterSpan) Tracer() opentracing.Tracer {
   247  	return span.tracer
   248  }
   249  
   250  // Deprecated: use LogFields or LogKV.
   251  func (span *adapterSpan) LogEvent(event string) {
   252  	// not implemented
   253  }
   254  
   255  // Deprecated: use LogFields or LogKV.
   256  func (span *adapterSpan) LogEventWithPayload(event string, payload interface{}) {
   257  	// not implemented
   258  }
   259  
   260  // Deprecated: use LogFields or LogKV.
   261  func (span *adapterSpan) Log(data opentracing.LogData) {
   262  	// not implemented
   263  }
   264  
   265  type stackdriverCloser struct {
   266  	exporter *stackdriver.Exporter
   267  }
   268  
   269  func (c stackdriverCloser) Close() error {
   270  	c.exporter.Flush()
   271  	return nil
   272  }
   273  
   274  type stackdriverConfig struct {
   275  	options *stackdriver.Options
   276  	sampler trace.Sampler
   277  }
   278  
   279  // https://pkg.go.dev/contrib.go.opencensus.io/exporter/stackdriver#Options
   280  var stackdriverConfigMapper = map[string]func(traceCfg *stackdriverConfig, value string) error{
   281  	"project_id": func(stackdriverCfg *stackdriverConfig, value string) error {
   282  		stackdriverCfg.options.ProjectID = value
   283  		return nil
   284  	},
   285  	"location": func(stackdriverCfg *stackdriverConfig, value string) error {
   286  		stackdriverCfg.options.Location = value
   287  		return nil
   288  	},
   289  	"bundle_delay_threshold": func(stackdriverCfg *stackdriverConfig, value string) error {
   290  		d, err := time.ParseDuration(value)
   291  		if err != nil {
   292  			return err
   293  		}
   294  		stackdriverCfg.options.BundleDelayThreshold = d
   295  		return nil
   296  	},
   297  	"bundle_count_threshold": func(stackdriverCfg *stackdriverConfig, value string) error {
   298  		v, err := strconv.Atoi(value)
   299  		if err != nil {
   300  			return err
   301  		}
   302  		stackdriverCfg.options.BundleCountThreshold = v
   303  		return nil
   304  	},
   305  	"trace_spans_buffer_max_bytes": func(stackdriverCfg *stackdriverConfig, value string) error {
   306  		v, err := strconv.Atoi(value)
   307  		if err != nil {
   308  			return err
   309  		}
   310  		stackdriverCfg.options.TraceSpansBufferMaxBytes = v
   311  		return nil
   312  	},
   313  	"timeout": func(stackdriverCfg *stackdriverConfig, value string) error {
   314  		d, err := time.ParseDuration(value)
   315  		if err != nil {
   316  			return err
   317  		}
   318  		stackdriverCfg.options.Timeout = d
   319  		return nil
   320  	},
   321  	"number_of_workers": func(stackdriverCfg *stackdriverConfig, value string) error {
   322  		v, err := strconv.Atoi(value)
   323  		if err != nil {
   324  			return err
   325  		}
   326  		stackdriverCfg.options.NumberOfWorkers = v
   327  		return nil
   328  	},
   329  	"sampler_probability": func(stackdriverCfg *stackdriverConfig, value string) error {
   330  		v, err := strconv.ParseFloat(value, 64)
   331  		if err != nil {
   332  			return err
   333  		}
   334  		stackdriverCfg.sampler = trace.ProbabilitySampler(v)
   335  		return nil
   336  	},
   337  }
   338  
   339  func stackdriverTracerFactory(config map[string]string) (opentracing.Tracer, io.Closer, error) {
   340  	stackdriverCfg := &stackdriverConfig{
   341  		options: &stackdriver.Options{},
   342  		sampler: trace.NeverSample(),
   343  	}
   344  
   345  	for k, v := range config {
   346  		mapper := stackdriverConfigMapper[k]
   347  		if mapper != nil {
   348  			err := mapper(stackdriverCfg, v)
   349  			if err != nil {
   350  				return nil, nil, err
   351  			}
   352  		} else {
   353  			log.Printf("stackdriver tracer: warning: ignoring unknown configuration option: %s", k)
   354  		}
   355  	}
   356  
   357  	exporter, err := stackdriver.NewExporter(*stackdriverCfg.options)
   358  	if err != nil {
   359  		return nil, nil, err
   360  	}
   361  
   362  	trace.RegisterExporter(exporter)
   363  	trace.ApplyConfig(trace.Config{DefaultSampler: stackdriverCfg.sampler})
   364  
   365  	return &adapterTracer{exporter}, &stackdriverCloser{exporter}, nil
   366  }
   367  
   368  func init() {
   369  	registerTracer("stackdriver", stackdriverTracerFactory)
   370  }