github.com/thanos-io/thanos@v0.32.5/pkg/tracing/stackdriver/tracer.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package stackdriver
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  
    12  	trace "cloud.google.com/go/trace/apiv1"
    13  	pb "cloud.google.com/go/trace/apiv1/tracepb"
    14  	"github.com/go-kit/log"
    15  	"github.com/go-kit/log/level"
    16  	"github.com/googleapis/gax-go"
    17  	gcloudtracer "github.com/lovoo/gcloud-opentracing"
    18  	"github.com/opentracing/basictracer-go"
    19  	"github.com/opentracing/opentracing-go"
    20  	"github.com/prometheus/common/version"
    21  
    22  	"github.com/thanos-io/thanos/pkg/tracing"
    23  )
    24  
    25  type tracer struct {
    26  	serviceName string
    27  	wrapped     opentracing.Tracer
    28  }
    29  
    30  // GetTraceIDFromSpanContext return TraceID from span.Context.
    31  func (t *tracer) GetTraceIDFromSpanContext(ctx opentracing.SpanContext) (string, bool) {
    32  	if c, ok := ctx.(basictracer.SpanContext); ok {
    33  		// "%016x%016x" - ugly hack for gcloud find traces by ID https://console.cloud.google.com/traces/traces?project=<project_id>&tid=<62119f61b7c2663962119f61b7c26639>.
    34  		return fmt.Sprintf("%016x%016x", c.TraceID, c.TraceID), true
    35  	}
    36  	return "", false
    37  }
    38  
    39  func (t *tracer) StartSpan(operationName string, opts ...opentracing.StartSpanOption) opentracing.Span {
    40  	span := t.wrapped.StartSpan(operationName, opts...)
    41  
    42  	if t.serviceName != "" {
    43  		span.SetTag("service_name", t.serviceName)
    44  	}
    45  
    46  	// Set common tags.
    47  	if hostname := os.Getenv("HOSTNAME"); hostname != "" {
    48  		span.SetTag("hostname", hostname)
    49  	}
    50  
    51  	span.SetTag("binary_revision", version.Revision)
    52  	if len(os.Args) > 1 {
    53  		span.SetTag("binary_cmd", os.Args[1])
    54  	}
    55  
    56  	return span
    57  }
    58  
    59  func (t *tracer) Extract(format, carrier interface{}) (opentracing.SpanContext, error) {
    60  	return t.wrapped.Extract(format, carrier)
    61  }
    62  
    63  func (t *tracer) Inject(sm opentracing.SpanContext, format, carrier interface{}) error {
    64  	return t.wrapped.Inject(sm, format, carrier)
    65  }
    66  
    67  type forceRecorder struct {
    68  	wrapped basictracer.SpanRecorder
    69  }
    70  
    71  // RecordSpan invokes wrapper SpanRecorder only if Sampled field is true or ForceTracingBaggageKey item is set in span's context.
    72  // NOTE(bplotka): Currently only HTTP supports ForceTracingBaggageKey injection on ForceTracingBaggageKey header existence.
    73  func (r *forceRecorder) RecordSpan(sp basictracer.RawSpan) {
    74  	if force := sp.Context.Baggage[tracing.ForceTracingBaggageKey]; force != "" {
    75  		sp.Context.Sampled = true
    76  	}
    77  
    78  	// All recorder implementation should support handling sp.Context.Sampled.
    79  	r.wrapped.RecordSpan(sp)
    80  }
    81  
    82  type gcloudRecorderLogger struct {
    83  	logger log.Logger
    84  }
    85  
    86  func (l *gcloudRecorderLogger) Infof(format string, args ...interface{}) {
    87  	level.Info(l.logger).Log("msg", fmt.Sprintf(format, args...))
    88  }
    89  
    90  func (l *gcloudRecorderLogger) Errorf(format string, args ...interface{}) {
    91  	level.Error(l.logger).Log("msg", fmt.Sprintf(format, args...))
    92  }
    93  
    94  // TODO(bwplotka): gcloudtracer is archived. Find replacement. For now wrap traceClient for compatibility.
    95  type compTraceWrapper struct {
    96  	cl *trace.Client
    97  }
    98  
    99  func (w *compTraceWrapper) PatchTraces(ctx context.Context, r *pb.PatchTracesRequest, _ ...gax.CallOption) error {
   100  	// Opts are never used in `gcloudtracer.NewRecorder`.
   101  	return w.cl.PatchTraces(ctx, r)
   102  }
   103  
   104  func (w *compTraceWrapper) Close() error {
   105  	return w.cl.Close()
   106  }
   107  
   108  func newGCloudTracer(ctx context.Context, logger log.Logger, gcloudTraceProjectID string, sampleFactor uint64, serviceName string) (opentracing.Tracer, io.Closer, error) {
   109  	traceClient, err := trace.NewClient(ctx)
   110  	if err != nil {
   111  		return nil, nil, err
   112  	}
   113  
   114  	// TODO(bwplotka): gcloudtracer is archived. Find replacement. For now wrap traceClient for compatibility.
   115  	r, err := gcloudtracer.NewRecorder(
   116  		ctx,
   117  		gcloudTraceProjectID,
   118  		&compTraceWrapper{cl: traceClient},
   119  		gcloudtracer.WithLogger(&gcloudRecorderLogger{logger: logger}))
   120  	if err != nil {
   121  		return nil, nil, err
   122  	}
   123  
   124  	shouldSample := func(traceID uint64) bool {
   125  		// Set the sampling rate.
   126  		return traceID%sampleFactor == 0
   127  	}
   128  	if sampleFactor < 1 {
   129  		level.Debug(logger).Log("msg", "Tracing is enabled, but sampling is 0 which means only spans with 'force tracing' baggage will enable tracing.")
   130  		shouldSample = func(_ uint64) bool {
   131  			return false
   132  		}
   133  	}
   134  	return &tracer{
   135  		serviceName: serviceName,
   136  		wrapped: basictracer.NewWithOptions(basictracer.Options{
   137  			ShouldSample:   shouldSample,
   138  			Recorder:       &forceRecorder{wrapped: r},
   139  			MaxLogsPerSpan: 100,
   140  		}),
   141  	}, r, nil
   142  }