vitess.io/vitess@v0.16.2/go/trace/trace.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package trace contains a helper interface that allows various tracing
    18  // tools to be plugged in to components using this interface. If no plugin is
    19  // registered, the default one makes all trace calls into no-ops.
    20  package trace
    21  
    22  import (
    23  	"context"
    24  	"fmt"
    25  	"io"
    26  	"strings"
    27  
    28  	"github.com/spf13/pflag"
    29  	"google.golang.org/grpc"
    30  
    31  	"vitess.io/vitess/go/vt/log"
    32  	"vitess.io/vitess/go/vt/vterrors"
    33  )
    34  
    35  // Span represents a unit of work within a trace. After creating a Span with
    36  // NewSpan(), call one of the Start methods to mark the beginning of the work
    37  // represented by this Span. Call Finish() when that work is done to record the
    38  // Span. A Span may be reused by calling Start again.
    39  type Span interface {
    40  	Finish()
    41  	// Annotate records a key/value pair associated with a Span. It should be
    42  	// called between Start and Finish.
    43  	Annotate(key string, value any)
    44  }
    45  
    46  // NewSpan creates a new Span with the currently installed tracing plugin.
    47  // If no tracing plugin is installed, it returns a fake Span that does nothing.
    48  func NewSpan(inCtx context.Context, label string) (Span, context.Context) {
    49  	parent, _ := currentTracer.FromContext(inCtx)
    50  	span := currentTracer.New(parent, label)
    51  	outCtx := currentTracer.NewContext(inCtx, span)
    52  
    53  	return span, outCtx
    54  }
    55  
    56  // NewFromString creates a new Span with the currently installed tracing plugin, extracting the span context from
    57  // the provided string.
    58  func NewFromString(inCtx context.Context, parent, label string) (Span, context.Context, error) {
    59  	span, err := currentTracer.NewFromString(parent, label)
    60  	if err != nil {
    61  		return nil, nil, err
    62  	}
    63  	outCtx := currentTracer.NewContext(inCtx, span)
    64  	return span, outCtx, nil
    65  }
    66  
    67  // AnnotateSQL annotates information about a sql query in the span. This is done in a way
    68  // so as to not leak personally identifying information (PII), or sensitive personal information (SPI)
    69  func AnnotateSQL(span Span, strippedSQL fmt.Stringer) {
    70  	span.Annotate("sql-statement-type", strippedSQL.String())
    71  }
    72  
    73  // FromContext returns the Span from a Context if present. The bool return
    74  // value indicates whether a Span was present in the Context.
    75  func FromContext(ctx context.Context) (Span, bool) {
    76  	return currentTracer.FromContext(ctx)
    77  }
    78  
    79  // NewContext returns a context based on parent with a new Span value.
    80  func NewContext(parent context.Context, span Span) context.Context {
    81  	return currentTracer.NewContext(parent, span)
    82  }
    83  
    84  // CopySpan creates a new context from parentCtx, with only the trace span
    85  // copied over from spanCtx, if it has any. If not, parentCtx is returned.
    86  func CopySpan(parentCtx, spanCtx context.Context) context.Context {
    87  	if span, ok := FromContext(spanCtx); ok {
    88  		return NewContext(parentCtx, span)
    89  	}
    90  	return parentCtx
    91  }
    92  
    93  // AddGrpcServerOptions adds GRPC interceptors that read the parent span from the grpc packets
    94  func AddGrpcServerOptions(addInterceptors func(s grpc.StreamServerInterceptor, u grpc.UnaryServerInterceptor)) {
    95  	currentTracer.AddGrpcServerOptions(addInterceptors)
    96  }
    97  
    98  // AddGrpcClientOptions adds GRPC interceptors that add parent information to outgoing grpc packets
    99  func AddGrpcClientOptions(addInterceptors func(s grpc.StreamClientInterceptor, u grpc.UnaryClientInterceptor)) {
   100  	currentTracer.AddGrpcClientOptions(addInterceptors)
   101  }
   102  
   103  // tracingService is an interface for creating spans or extracting them from Contexts.
   104  type tracingService interface {
   105  	// New creates a new span from an existing one, if provided. The parent can also be nil
   106  	New(parent Span, label string) Span
   107  
   108  	// NewFromString creates a new span and uses the provided string to reconstitute the parent span
   109  	NewFromString(parent, label string) (Span, error)
   110  
   111  	// FromContext extracts a span from a context, making it possible to annotate the span with additional information
   112  	FromContext(ctx context.Context) (Span, bool)
   113  
   114  	// NewContext creates a new context containing the provided span
   115  	NewContext(parent context.Context, span Span) context.Context
   116  
   117  	// AddGrpcServerOptions allows a tracing system to add interceptors to grpc server traffic
   118  	AddGrpcServerOptions(addInterceptors func(s grpc.StreamServerInterceptor, u grpc.UnaryServerInterceptor))
   119  
   120  	// AddGrpcClientOptions allows a tracing system to add interceptors to grpc server traffic
   121  	AddGrpcClientOptions(addInterceptors func(s grpc.StreamClientInterceptor, u grpc.UnaryClientInterceptor))
   122  }
   123  
   124  // TracerFactory creates a tracing service for the service provided. It's important to close the provided io.Closer
   125  // object to make sure that all spans are sent to the backend before the process exits.
   126  type TracerFactory func(serviceName string) (tracingService, io.Closer, error)
   127  
   128  var (
   129  	// tracingBackendFactories should be added to by a plugin during init() to install itself
   130  	tracingBackendFactories = make(map[string]TracerFactory)
   131  
   132  	currentTracer tracingService = noopTracingServer{}
   133  
   134  	/* flags */
   135  
   136  	tracingServer = "noop"
   137  	enableLogging bool
   138  
   139  	pluginFlags []func(fs *pflag.FlagSet)
   140  )
   141  
   142  func RegisterFlags(fs *pflag.FlagSet) {
   143  	fs.StringVar(&tracingServer, "tracer", "noop", "tracing service to use")
   144  	fs.BoolVar(&enableLogging, "tracing-enable-logging", false, "whether to enable logging in the tracing service")
   145  
   146  	for _, fn := range pluginFlags {
   147  		fn(fs)
   148  	}
   149  }
   150  
   151  // StartTracing enables tracing for a named service
   152  func StartTracing(serviceName string) io.Closer {
   153  	factory, ok := tracingBackendFactories[tracingServer]
   154  	if !ok {
   155  		return fail(serviceName)
   156  	}
   157  
   158  	tracer, closer, err := factory(serviceName)
   159  	if err != nil {
   160  		log.Error(vterrors.Wrapf(err, "failed to create a %s tracer", tracingServer))
   161  		return &nilCloser{}
   162  	}
   163  
   164  	currentTracer = tracer
   165  	if tracingServer != "noop" {
   166  		log.Infof("successfully started tracing with [%s]", tracingServer)
   167  	}
   168  
   169  	return closer
   170  }
   171  
   172  func fail(serviceName string) io.Closer {
   173  	options := make([]string, len(tracingBackendFactories))
   174  	for k := range tracingBackendFactories {
   175  		options = append(options, k)
   176  	}
   177  	altStr := strings.Join(options, ", ")
   178  	log.Errorf("no such [%s] tracing service found. alternatives are: %v", serviceName, altStr)
   179  	return &nilCloser{}
   180  }
   181  
   182  type nilCloser struct {
   183  }
   184  
   185  func (c *nilCloser) Close() error { return nil }