github.com/facebookincubator/go-belt@v0.0.0-20230703220935-39cd348f1a38/tool/experimental/tracer/implementation/zipkin/span.go (about)

     1  // Copyright 2022 Meta Platforms, Inc. and affiliates.
     2  //
     3  // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
     4  //
     5  // 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
     6  //
     7  // 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
     8  //
     9  // 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
    10  //
    11  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    12  
    13  package zipkin
    14  
    15  import (
    16  	"context"
    17  	"encoding/json"
    18  	"fmt"
    19  	"strings"
    20  	"sync"
    21  	"time"
    22  
    23  	"github.com/facebookincubator/go-belt"
    24  	"github.com/facebookincubator/go-belt/pkg/field"
    25  	"github.com/facebookincubator/go-belt/tool/experimental/tracer"
    26  	"github.com/openzipkin/zipkin-go"
    27  	"github.com/openzipkin/zipkin-go/model"
    28  )
    29  
    30  func init() {
    31  	if tracer.Default == nil {
    32  		tracer.Default = func() tracer.Tracer {
    33  			return Default()
    34  		}
    35  	}
    36  }
    37  
    38  // SpanImpl is the implementation of tracer.Span on top of zipkin.Span.
    39  type SpanImpl struct {
    40  	sendOnce   sync.Once
    41  	parent     tracer.Span
    42  	name       string
    43  	tracer     *TracerImpl
    44  	zipkinSpan zipkin.Span
    45  	fields     *field.FieldsChain
    46  }
    47  
    48  var _ tracer.Span = (*SpanImpl)(nil)
    49  
    50  func (t *TracerImpl) newSpanBelt(
    51  	belt *belt.Belt,
    52  	name string,
    53  	parent tracer.Span,
    54  	options ...tracer.SpanOption,
    55  ) (tracer.Span, *belt.Belt) {
    56  	if tracer.IsNoopSpan(parent) {
    57  		return tracer.NewNoopSpan(name, parent, time.Now()), belt
    58  	}
    59  
    60  	span := &SpanImpl{
    61  		parent: parent,
    62  		tracer: t,
    63  		fields: t.ContextFields,
    64  		name:   name,
    65  	}
    66  
    67  	if !t.PreHooks.ProcessSpan(span) {
    68  		return tracer.NewNoopSpan(name, parent, time.Now()), belt
    69  	}
    70  
    71  	t.compileFields()
    72  
    73  	zipkinOptions := spanZipkinOptions(t.compiledFields, options...)
    74  	span.zipkinSpan = t.ZipkinTracer.StartSpan(name, zipkinOptions...)
    75  
    76  	var returnSpan tracer.Span = span
    77  	if zipkin.IsNoop(span.zipkinSpan) {
    78  		returnSpan = tracer.NewNoopSpan(name, parent, time.Now())
    79  	}
    80  
    81  	if belt == nil {
    82  		return returnSpan, nil
    83  	}
    84  	return returnSpan, tracer.BeltWithSpan(belt, span)
    85  }
    86  
    87  func (t *TracerImpl) newSpanCtx(
    88  	ctx context.Context,
    89  	name string,
    90  	parent tracer.Span,
    91  	options ...tracer.SpanOption,
    92  ) (tracer.Span, context.Context) {
    93  	if tracer.IsNoopSpan(parent) {
    94  		return tracer.NewNoopSpan(name, parent, time.Now()), ctx
    95  	}
    96  
    97  	span := &SpanImpl{
    98  		parent: parent,
    99  		tracer: t,
   100  		fields: t.ContextFields,
   101  		name:   name,
   102  	}
   103  
   104  	if !t.PreHooks.ProcessSpan(span) {
   105  		return tracer.NewNoopSpan(name, parent, time.Now()), ctx
   106  	}
   107  
   108  	t.compileFields()
   109  
   110  	zipkinOptions := spanZipkinOptions(t.compiledFields, options...)
   111  	var resultCtx context.Context
   112  	if ctx != nil {
   113  		span.zipkinSpan, resultCtx = t.ZipkinTracer.StartSpanFromContext(ctx, name, zipkinOptions...)
   114  	} else {
   115  		span.zipkinSpan = t.ZipkinTracer.StartSpan(name, zipkinOptions...)
   116  		resultCtx = zipkin.NewContext(context.Background(), span.zipkinSpan)
   117  	}
   118  
   119  	var returnSpan tracer.Span = span
   120  	if zipkin.IsNoop(span.zipkinSpan) {
   121  		returnSpan = tracer.NewNoopSpan(name, parent, time.Now())
   122  	}
   123  	resultCtx = tracer.CtxWithSpan(resultCtx, returnSpan)
   124  
   125  	return returnSpan, resultCtx
   126  }
   127  
   128  func spanZipkinOptions(fields map[string]string, opts ...tracer.SpanOption) []zipkin.SpanOption {
   129  	lastResetFieldsIdx := -1
   130  	for idx, opt := range opts {
   131  		if _, isResetFields := opt.(tracer.SpanOptionResetFields); isResetFields {
   132  			lastResetFieldsIdx = idx
   133  			continue
   134  		}
   135  	}
   136  	zipkinOptions := make([]zipkin.SpanOption, 0, len(opts)+1)
   137  	if fields != nil && lastResetFieldsIdx < 0 {
   138  		zipkinOptions = append(zipkinOptions, zipkin.Tags(fields))
   139  	}
   140  	for idx, opt := range opts {
   141  		if _, isAddFields := opt.(tracer.SpanOptionAddFields); isAddFields && idx < lastResetFieldsIdx {
   142  			continue
   143  		}
   144  		zipkinOption := spanOptionToZipkin(opt)
   145  		if zipkinOption == nil {
   146  			continue
   147  		}
   148  		zipkinOptions = append(zipkinOptions, zipkinOption)
   149  	}
   150  
   151  	return zipkinOptions
   152  }
   153  
   154  func spanOptionToZipkin(opt tracer.SpanOption) zipkin.SpanOption {
   155  	switch opt := opt.(type) {
   156  	case tracer.SpanOptionAddFields:
   157  		tags := make(map[string]string, len(opt))
   158  		field.Fields(opt).ForEachField(func(f *field.Field) bool {
   159  			tags[f.Key] = fmt.Sprint(f.Value)
   160  			return true
   161  		})
   162  		return zipkin.Tags(tags)
   163  	case tracer.SpanOptionResetFields:
   164  		return nil
   165  	case tracer.SpanOptionRole:
   166  		switch opt {
   167  		case tracer.RoleClient:
   168  			return zipkin.Kind(model.Client)
   169  		case tracer.RoleServer:
   170  			return zipkin.Kind(model.Server)
   171  		default:
   172  			return zipkin.Kind(model.Kind(strings.ToUpper(string(opt))))
   173  		}
   174  	case tracer.SpanOptionStart:
   175  		return zipkin.StartTime(time.Time(opt))
   176  	}
   177  	return nil
   178  }
   179  
   180  // ID implements tracer.Span.
   181  func (span *SpanImpl) ID() any {
   182  	return span.zipkinSpan.Context().ID
   183  }
   184  
   185  // Name implements tracer.Span.
   186  func (span *SpanImpl) Name() string {
   187  	return span.name
   188  }
   189  
   190  // TraceIDs implements tracer.Span.
   191  func (span *SpanImpl) TraceIDs() belt.TraceIDs {
   192  	return span.tracer.TraceIDs
   193  }
   194  
   195  // StartTS implements tracer.Span.
   196  func (span *SpanImpl) StartTS() time.Time {
   197  	b, err := json.Marshal(span.zipkinSpan)
   198  	if err != nil {
   199  		return time.Time{}
   200  	}
   201  
   202  	var spanModel model.SpanModel
   203  	err = json.Unmarshal(b, &spanModel)
   204  	if err != nil {
   205  		return time.Time{}
   206  	}
   207  
   208  	return spanModel.Timestamp
   209  }
   210  
   211  // Fields implements tracer.Span.
   212  func (span *SpanImpl) Fields() field.AbstractFields {
   213  	return span.fields
   214  }
   215  
   216  // Parent implements tracer.Span.
   217  func (span *SpanImpl) Parent() tracer.Span {
   218  	return span.parent
   219  }
   220  
   221  // SetName implements tracer.Span.
   222  func (span *SpanImpl) SetName(name string) {
   223  	span.zipkinSpan.SetName(name)
   224  }
   225  
   226  // Annotate implements tracer.Span.
   227  func (span *SpanImpl) Annotate(ts time.Time, event string) {
   228  	span.zipkinSpan.Annotate(ts, event)
   229  }
   230  
   231  // SetField implements tracer.Span.
   232  func (span *SpanImpl) SetField(k field.Key, v field.Value) {
   233  	span.setFieldInZipkin(k, v)
   234  }
   235  
   236  func (span *SpanImpl) setFieldInZipkin(k field.Key, v field.Value) {
   237  	span.fields = span.fields.WithField(k, v)
   238  	span.zipkinSpan.Tag(k, fmt.Sprint(v))
   239  }
   240  
   241  // SetFields implements tracer.Span.
   242  func (span *SpanImpl) SetFields(fields field.AbstractFields) {
   243  	span.fields = span.fields.WithFields(fields)
   244  	fields.ForEachField(func(f *field.Field) bool {
   245  		span.setFieldInZipkin(f.Key, f.Value)
   246  		return true
   247  	})
   248  }
   249  
   250  // Finish implements tracer.Span.
   251  func (span *SpanImpl) Finish() {
   252  	span.sendOnce.Do(func() {
   253  		if !span.tracer.Hooks.ProcessSpan(span) {
   254  			return
   255  		}
   256  		span.zipkinSpan.Finish()
   257  	})
   258  }
   259  
   260  // FinishWithDuration implements tracer.Span.
   261  func (span *SpanImpl) FinishWithDuration(duration time.Duration) {
   262  	span.sendOnce.Do(func() {
   263  		if !span.tracer.Hooks.ProcessSpan(span) {
   264  			return
   265  		}
   266  		span.zipkinSpan.FinishedWithDuration(duration)
   267  	})
   268  }
   269  
   270  // SendAsIs implements tracer.Span.
   271  func (span *SpanImpl) SendAsIs() {
   272  	span.sendOnce.Do(span.zipkinSpan.Flush)
   273  }
   274  
   275  // Flush implements tracer.Span.
   276  func (*SpanImpl) Flush() {}