github.com/facebookincubator/go-belt@v0.0.0-20230703220935-39cd348f1a38/tool/experimental/tracer/implementation/logger/tracer.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 logger
    14  
    15  import (
    16  	"context"
    17  	"time"
    18  
    19  	"github.com/facebookincubator/go-belt"
    20  	"github.com/facebookincubator/go-belt/pkg/field"
    21  	"github.com/facebookincubator/go-belt/tool/experimental/tracer"
    22  	"github.com/facebookincubator/go-belt/tool/logger"
    23  	loggertypes "github.com/facebookincubator/go-belt/tool/logger/types"
    24  )
    25  
    26  // TracerImpl is the implementation of tracer.Tracer based on a given logger.Logger.
    27  type TracerImpl struct {
    28  	Logger    loggertypes.Logger
    29  	LevelFunc func(span *SpanImpl) loggertypes.Level
    30  	PreHooks  tracer.Hooks
    31  	Hooks     tracer.Hooks
    32  	TraceIDs  belt.TraceIDs
    33  	Fields    *field.FieldsChain
    34  }
    35  
    36  var _ tracer.Tracer = (*TracerImpl)(nil)
    37  
    38  func (t TracerImpl) clone() *TracerImpl {
    39  	return &t
    40  }
    41  
    42  // WithContextFields implements tracer.Tracer.
    43  func (t *TracerImpl) WithContextFields(allFields *field.FieldsChain, newFieldsCount int) belt.Tool {
    44  	clone := t.clone()
    45  	clone.Fields = allFields
    46  	return clone
    47  }
    48  
    49  // WithTraceIDs implements tracer.Tracer.
    50  func (t *TracerImpl) WithTraceIDs(traceIDs belt.TraceIDs, newTraceIDsCount int) belt.Tool {
    51  	clone := t.clone()
    52  	clone.TraceIDs = traceIDs
    53  	clone.Logger = t.Logger.WithTraceIDs(traceIDs, newTraceIDsCount).(logger.Logger)
    54  	return clone
    55  }
    56  
    57  // WithPreHooks implements tracer.Tracer.
    58  func (t *TracerImpl) WithPreHooks(hooks ...tracer.Hook) tracer.Tracer {
    59  	c := t.clone()
    60  	if hooks == nil {
    61  		c.PreHooks = nil
    62  	} else {
    63  		c.PreHooks = tracer.Hooks{c.PreHooks, tracer.Hooks(hooks)}
    64  	}
    65  	return c
    66  }
    67  
    68  // WithHooks implements tracer.Tracer.
    69  func (t *TracerImpl) WithHooks(hooks ...tracer.Hook) tracer.Tracer {
    70  	c := t.clone()
    71  	if hooks == nil {
    72  		c.Hooks = nil
    73  	} else {
    74  		c.Hooks = tracer.Hooks{c.Hooks, tracer.Hooks(hooks)}
    75  	}
    76  	return c
    77  }
    78  
    79  // Start implements tracer.Tracer.
    80  func (t *TracerImpl) Start(name string, parent tracer.Span, options ...tracer.SpanOption) tracer.Span {
    81  	span, _ := t.newSpanBelt(nil, name, parent, options...)
    82  	return span
    83  }
    84  
    85  // StartWithBelt implements tracer.Tracer.
    86  func (t *TracerImpl) StartWithBelt(belt *belt.Belt, name string, options ...tracer.SpanOption) (tracer.Span, *belt.Belt) {
    87  	return t.newSpanBelt(belt, name, nil, options...)
    88  
    89  }
    90  
    91  // StartChildWithBelt implements tracer.Tracer.
    92  func (t *TracerImpl) StartChildWithBelt(belt *belt.Belt, name string, options ...tracer.SpanOption) (tracer.Span, *belt.Belt) {
    93  	return t.newSpanBelt(belt, name, tracer.SpanFromBelt(belt), options...)
    94  }
    95  
    96  // StartWithCtx implements tracer.Tracer.
    97  func (t *TracerImpl) StartWithCtx(ctx context.Context, name string, options ...tracer.SpanOption) (tracer.Span, context.Context) {
    98  	return t.newSpanCtx(ctx, name, nil, options...)
    99  
   100  }
   101  
   102  // StartChildWithCtx implements tracer.Tracer.
   103  func (t *TracerImpl) StartChildWithCtx(ctx context.Context, name string, options ...tracer.SpanOption) (tracer.Span, context.Context) {
   104  	return t.newSpanCtx(ctx, name, tracer.SpanFromCtx(ctx), options...)
   105  }
   106  
   107  // Flush implements tracer.Tracer.
   108  func (t *TracerImpl) Flush() {
   109  	t.Logger.Flush()
   110  }
   111  
   112  func (t *TracerImpl) send(span *SpanImpl) {
   113  	fields := span.FieldsValue.
   114  		WithField(FieldNameName, span.NameValue, FieldPropertyName).
   115  		WithField(FieldNameDuration, span.Duration, FieldPropertyDuration).
   116  		WithField(FieldNameStartTS, span.StartTSValue, FieldPropertyStartTS)
   117  
   118  	if span.ParentValue != nil {
   119  		fields = fields.WithField(FieldNameParent, span.ParentValue, FieldPropertyParent)
   120  	}
   121  
   122  	if len(span.Events) > 0 {
   123  		eventsAsFields := make(field.Fields, 0, len(span.Events))
   124  		for _, ev := range span.Events {
   125  			eventsAsFields = append(eventsAsFields, field.Field{
   126  				Key:        FieldNamePrefixEvent + ev.Name,
   127  				Value:      ev.Timestamp,
   128  				Properties: field.Properties{FieldPropertyEvent},
   129  			})
   130  		}
   131  		fields = fields.WithFields(eventsAsFields)
   132  	}
   133  
   134  	t.Logger.LogFields(t.LevelFunc(span), "timespan finished", fields)
   135  }
   136  
   137  type fieldPropertyEnum uint
   138  
   139  const (
   140  	// FieldPropertyStartTS is a field.Property which marks the field as a start timestamp.
   141  	FieldPropertyStartTS = fieldPropertyEnum(iota + 1)
   142  
   143  	// FieldPropertyDuration is a field.Property which marks the field as a duration.
   144  	FieldPropertyDuration
   145  
   146  	// FieldPropertyName is a field.Property which marks the field as the span name
   147  	FieldPropertyName
   148  
   149  	// FieldPropertyEvent is a field.Property which marks the field as the timestamp of an event.
   150  	FieldPropertyEvent
   151  
   152  	// FieldPropertyParent is a field.Property which marks the field as the parent span.
   153  	FieldPropertyParent
   154  )
   155  
   156  // SpanFromLogEntry converts a logger Entry to a Span.
   157  func SpanFromLogEntry(entry *loggertypes.Entry) *SpanImpl {
   158  	var (
   159  		fields   field.Fields
   160  		startTS  time.Time
   161  		duration time.Duration
   162  		parent   tracer.Span
   163  		name     string
   164  		events   []Event
   165  	)
   166  	entry.Fields.ForEachField(func(f *field.Field) bool {
   167  		if len(f.Properties) != 1 {
   168  			fields = append(fields, *f)
   169  			return true
   170  		}
   171  		switch f.Properties[0] {
   172  		case FieldPropertyStartTS:
   173  			startTS = f.Value.(time.Time)
   174  		case FieldPropertyParent:
   175  			parent = f.Value.(tracer.Span)
   176  		case FieldPropertyDuration:
   177  			duration = f.Value.(time.Duration)
   178  		case FieldPropertyName:
   179  			name = f.Value.(string)
   180  		case FieldPropertyEvent:
   181  			events = append(events, Event{
   182  				Timestamp: f.Value.(time.Time),
   183  				Name:      f.Key[len(FieldNamePrefixEvent):],
   184  			})
   185  		default:
   186  			fields = append(fields, *f)
   187  		}
   188  		return true
   189  	})
   190  	span := &SpanImpl{
   191  		StartTSValue: startTS,
   192  		Duration:     duration,
   193  		ParentValue:  parent,
   194  		FieldsValue:  (*field.FieldsChain)(nil).WithFields(fields),
   195  		NameValue:    name,
   196  		Events:       events,
   197  	}
   198  	return span
   199  }
   200  
   201  var (
   202  	// FieldNamePrefixEvent is the field name prefix for events reported through Annotate method.
   203  	FieldNamePrefixEvent = "event_"
   204  
   205  	// FieldNameName is the field name for the span name.
   206  	FieldNameName = "name"
   207  
   208  	// FieldNameDuration is the field name for the duration.
   209  	FieldNameDuration = "duration"
   210  
   211  	// FieldNameParent is the field name for the parent span.
   212  	FieldNameParent = "parent"
   213  
   214  	// FieldNameStartTS is the field name for the span start timestamp.
   215  	FieldNameStartTS = "start_ts"
   216  )
   217  
   218  // Default is the overridable function which returns a logger-based Tracer
   219  // with the default configuration.
   220  var Default = func() tracer.Tracer {
   221  	return New(logger.Default(), func(_ *SpanImpl) loggertypes.Level {
   222  		return logger.LevelTrace
   223  	})
   224  }
   225  
   226  // New returns a new instance of TracerImpl (a tracer.Tracer implementation based on a given logger.Logger).
   227  func New(logger loggertypes.Logger, levelFunc func(span *SpanImpl) loggertypes.Level) *TracerImpl {
   228  	return &TracerImpl{
   229  		Logger:    logger,
   230  		LevelFunc: levelFunc,
   231  	}
   232  }