go.dedis.ch/onet/v3@v3.2.11-0.20210930124529-e36530bca7ef/tracing/trace.go (about)

     1  package tracing
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"path"
     9  	"reflect"
    10  	"regexp"
    11  	"strconv"
    12  	"strings"
    13  )
    14  
    15  // The traceWrapper and spanWrapper structures simulate a real tracing
    16  // environment using the calls from onet/log.
    17  // They are the same as defined by honeycomb:
    18  // A trace contains one or more spans.
    19  // A trace represents one specific interaction from the user.
    20  // The spans are the different steps done to fulfil that interaction.
    21  //
    22  // In a real tracing environment,
    23  // context.Context is handled down from the entry point to the sub-modules.
    24  // This context is then used to link the spans together into traces.
    25  // As we don't want to rewrite all of onet and cothority to use tracing,
    26  // these Wrappers use a trick to simulate the contexts:
    27  // The golang's stack-trace has enough information to guess quite accurately
    28  // (not 100%, but 95% of the time) if the call to onet/log comes from a new
    29  // trace or not.
    30  // Using the information in the golang stack-trace,
    31  // a context.Context environment is created and then passed to the tracing
    32  // library.
    33  //
    34  // Currently there are two implementations of the Trace and Span:
    35  //  - simul for tests that collect all traces in a simulCollector
    36  //  - honeycomb that sends the traces to honeycomb
    37  // It should be possible to add other tracing modules,
    38  // like prometheus.io or zipkin.io.
    39  // The implementation of these tracers is left as an exercise to the reader...
    40  
    41  // Trace interfaces the methods that are used by the wrappers,
    42  // for easier testing and eventual usage of other tools.
    43  type Trace interface {
    44  	// AddFiled adds a key/val to the trace.
    45  	//This key/val pair will be set to all spans contained in this trace.
    46  	AddField(key string, val interface{})
    47  	// There is at least one span for each trace, which is the root span.
    48  	GetRootSpan() Span
    49  	// Finishes the trace and sends it to the main service.
    50  	// Once Send is called,
    51  	//further interactions with the Trace structure are not defined.
    52  	Send()
    53  }
    54  
    55  // Span interfaces the methods that are used by the wrappers,
    56  // for easier testing and eventual usage of other tools than HoneyComb.
    57  type Span interface {
    58  	AddField(key string, val interface{})
    59  	CreateChild(ctx context.Context) (context.Context, Span)
    60  	Send()
    61  }
    62  
    63  // traceWrapper keeps track of the stackEntries and creates / sends Spans as
    64  // needed.
    65  type traceWrapper struct {
    66  	ctx     context.Context
    67  	root    *spanWrapper
    68  	hcTrace Trace
    69  }
    70  
    71  func newTraceWrapper(ctx context.Context, tr Trace,
    72  	ses []stackEntry) (tw *traceWrapper, child *spanWrapper) {
    73  	tw = &traceWrapper{
    74  		ctx:     ctx,
    75  		hcTrace: tr,
    76  	}
    77  	tw.root, child = newRootSpanWrapper(tw, ses)
    78  	return
    79  }
    80  
    81  func (tw *traceWrapper) add(ns string, val interface{}) {
    82  	structToFields(tw.hcTrace.AddField, ns, val)
    83  }
    84  
    85  func (tw *traceWrapper) stackToSpan(stack []stackEntry) *spanWrapper {
    86  	return tw.root.stackToSpan(stack)
    87  }
    88  
    89  func (tw *traceWrapper) send() {
    90  	tw.hcTrace.Send()
    91  }
    92  
    93  func (tw *traceWrapper) ses() (ses []stackEntry) {
    94  	sw := tw.root
    95  	for {
    96  		ses = append(ses, sw.se)
    97  		if sw.child == nil {
    98  			return
    99  		}
   100  		sw = sw.child
   101  	}
   102  }
   103  
   104  func (tw *traceWrapper) findGoroutine(in []stackEntry) []stackEntry {
   105  	twSes := tw.ses()
   106  	for t, set := range twSes {
   107  		if in[0].samePkgMethodStr(set) {
   108  			merged := append(twSes[0:t+1], in...)
   109  			for m := range merged[t+1:] {
   110  				merged[t+m].traceID = twSes[0].traceID
   111  			}
   112  			return merged
   113  		}
   114  	}
   115  	return nil
   116  }
   117  
   118  // spanWrapper keeps track of the contexts created for the different spans
   119  // and closes children as new spans arrive.
   120  type spanWrapper struct {
   121  	se     stackEntry
   122  	ctx    context.Context
   123  	trace  *traceWrapper
   124  	parent *spanWrapper
   125  	child  *spanWrapper
   126  	hcSpan Span
   127  }
   128  
   129  func newSpanWrapper(ctx context.Context, t *traceWrapper, hcs Span, se stackEntry) *spanWrapper {
   130  	newSpan := &spanWrapper{
   131  		ctx:    ctx,
   132  		trace:  t,
   133  		hcSpan: hcs,
   134  		se:     se,
   135  	}
   136  	se.addFields(hcs)
   137  	return newSpan
   138  }
   139  
   140  func newRootSpanWrapper(t *traceWrapper, stack []stackEntry) (root,
   141  	child *spanWrapper) {
   142  	root = newSpanWrapper(t.ctx, t, t.hcTrace.GetRootSpan(), stack[0])
   143  	if len(stack) > 1 {
   144  		child = root.stackToSpan(stack)
   145  	} else {
   146  		child = root
   147  	}
   148  	return
   149  }
   150  
   151  func (sw *spanWrapper) add(ns string, val interface{}) {
   152  	structToFields(sw.hcSpan.AddField, ns, val)
   153  }
   154  
   155  func (sw *spanWrapper) log(lvl int, msg string) {
   156  	if sw.child != nil {
   157  		sw.child.done()
   158  	}
   159  	_, sLog := sw.hcSpan.CreateChild(sw.ctx)
   160  	msgStruct := struct {
   161  		Lvl int
   162  		Msg string
   163  	}{lvl, msg}
   164  	// This value is unfortunately not exported from onet/log/lvl.go
   165  	if lvl > -17 {
   166  		structToFields(sLog.AddField, "log", msgStruct)
   167  	} else {
   168  		structToFields(sLog.AddField, "error", msgStruct)
   169  	}
   170  	sw.se.addFields(sLog)
   171  	sLog.Send()
   172  }
   173  
   174  func (sw *spanWrapper) done() {
   175  	if sw.child != nil {
   176  		sw.child.done()
   177  	}
   178  	sw.parent.child = nil
   179  	sw.hcSpan.Send()
   180  }
   181  
   182  func (sw *spanWrapper) updateSE(se stackEntry) {
   183  	sw.se = se
   184  	se.addFields(sw.hcSpan)
   185  }
   186  
   187  func (sw *spanWrapper) stackToSpan(stack []stackEntry) *spanWrapper {
   188  	if len(stack) == 1 {
   189  		sw.updateSE(stack[0])
   190  		return sw
   191  	}
   192  	if sw.child != nil {
   193  		if sw.child.se.traceID != stack[1].traceID ||
   194  			stack[0].line != sw.se.line {
   195  			sw.child.done()
   196  		}
   197  	}
   198  	if sw.child == nil {
   199  		sw.child = sw.createChild(stack[1])
   200  		sw.updateSE(stack[0])
   201  	}
   202  	return sw.child.stackToSpan(stack[1:])
   203  }
   204  
   205  func (sw *spanWrapper) createChild(se stackEntry) *spanWrapper {
   206  	ctx, newHCS := sw.hcSpan.CreateChild(sw.ctx)
   207  	newChild := newSpanWrapper(ctx, sw.trace, newHCS, se)
   208  	newChild.parent = sw
   209  	return newChild
   210  }
   211  
   212  type stackEntry struct {
   213  	line      int
   214  	path      string
   215  	file      string
   216  	pkgPath   string
   217  	method    string
   218  	params    string
   219  	traceID   string
   220  	createdBy bool
   221  }
   222  
   223  var stackFileLine = regexp.MustCompile(
   224  	`\s*(.*):([0-9]*)( \+0x[a-f0-9]*$)?\s*`)
   225  var stackPkgMethod = regexp.MustCompile(
   226  	`\s*(created by )?(.*)\.(.*?\w)(\(.*\))?\s*$`)
   227  var methodParams = regexp.MustCompile(`\(\)$`)
   228  
   229  func newStackEntry(method, file, traceID string) (se stackEntry, err error) {
   230  	se.traceID = traceID
   231  	fileLine := stackFileLine.FindStringSubmatch(file)
   232  	if len(fileLine) < 2 {
   233  		return se, errors.New("didn't find file and line number")
   234  	}
   235  	se.line, err = strconv.Atoi(fileLine[2])
   236  	if err != nil {
   237  		se.line = -1
   238  	}
   239  	se.path, se.file = path.Split(fileLine[1])
   240  
   241  	pmp := stackPkgMethod.FindStringSubmatch(method)
   242  	if len(pmp) < 4 {
   243  		return se, errors.New("didn't find pkg and method")
   244  	}
   245  	se.createdBy = pmp[1] != ""
   246  	se.pkgPath = pmp[2]
   247  	se.method = pmp[3]
   248  	if len(pmp) == 5 {
   249  		se.params = methodParams.ReplaceAllString(pmp[4], "")
   250  	}
   251  	return se, nil
   252  }
   253  
   254  var stackGoID = regexp.MustCompile(`goroutine (.*) \[(.*)\]`)
   255  
   256  func getGoID(stack string) string {
   257  	id := stackGoID.FindStringSubmatch(strings.Split(stack, "\n")[0])
   258  	if len(id) != 3 {
   259  		return "unknownID"
   260  	}
   261  	return id[1]
   262  }
   263  
   264  func parseLogs(stack string, idMap map[string]string) (ses []stackEntry) {
   265  	var lvl []string
   266  	// prepend "go " or "id " to differentiate an automatic ID from a real one.
   267  	traceID := getGoID(stack)
   268  	if tid, ok := idMap[traceID]; ok {
   269  		traceID = "id_" + tid
   270  	} else {
   271  		traceID = "go_" + traceID
   272  	}
   273  	stackLines := strings.Split(stack, "\n")
   274  	for _, s := range stackLines[1:] {
   275  		lvl = append(lvl, s)
   276  		if len(lvl) == 2 {
   277  			se, err := newStackEntry(lvl[0], lvl[1], traceID)
   278  			if err == nil {
   279  				ses = append([]stackEntry{se}, ses...)
   280  			}
   281  			lvl = []string{}
   282  		}
   283  	}
   284  	return
   285  }
   286  
   287  func mergeLogs(known map[string]*traceWrapper,
   288  	in []stackEntry) (out []stackEntry) {
   289  	// First check if there is a common traceID - else check if there is a
   290  	// go-routine we can attach to.
   291  	for idIn, seIn := range in {
   292  		for _, tw := range known {
   293  			if seIn.traceID == tw.root.se.traceID {
   294  				if out = tw.findGoroutine(in[idIn:]); out != nil {
   295  					return
   296  				}
   297  			}
   298  		}
   299  		// No common traceID found - check goroutine calls
   300  		for _, tw := range known {
   301  			if strings.HasPrefix(tw.root.se.traceID, "id_") ||
   302  				strings.HasPrefix(seIn.traceID, "id_") {
   303  				continue
   304  			}
   305  			if out = tw.findGoroutine(in[idIn:]); out != nil {
   306  				return
   307  			}
   308  		}
   309  	}
   310  	return in
   311  }
   312  
   313  func (se stackEntry) pkgMethod() string {
   314  	return fmt.Sprintf("%s.%s", se.pkgPath, se.method)
   315  }
   316  
   317  func (se stackEntry) checkEntryPoint(eps []string) (entryPoint string) {
   318  	for _, entryPoint = range eps {
   319  		if strings.Contains(se.pkgMethod(), entryPoint) {
   320  			return
   321  		}
   322  	}
   323  	return ""
   324  }
   325  
   326  func (se stackEntry) pkgMethodStr() string {
   327  	return fmt.Sprintf("%s.%s", se.pkgPath, se.method)
   328  }
   329  
   330  func (se stackEntry) samePkgMethodStr(other stackEntry) bool {
   331  	return se.pkgMethodStr() == other.pkgMethodStr()
   332  }
   333  
   334  func (se stackEntry) uniqueID() string {
   335  	return fmt.Sprintf("%s - %s.%s%s", se.traceID, se.pkgPath, se.method,
   336  		se.params)
   337  }
   338  
   339  func (se stackEntry) addFields(s Span) {
   340  	if se.params != "" {
   341  		s.AddField("params", se.params)
   342  	}
   343  	if se.method != "" {
   344  		s.AddField("method", se.method)
   345  	}
   346  	if se.pkgPath != "" {
   347  		s.AddField("pkgPath", se.pkgPath)
   348  	}
   349  	if se.file != "" {
   350  		s.AddField("file", se.file)
   351  	}
   352  	if se.path != "" {
   353  		s.AddField("path", se.path)
   354  	}
   355  	if se.line != 0 {
   356  		s.AddField("line", se.line)
   357  	}
   358  }
   359  
   360  func (se stackEntry) String() string {
   361  	return fmt.Sprintf("%s:%d", se.method, se.line)
   362  }
   363  
   364  type fieldAdder = func(key string, val interface{})
   365  
   366  func structToFields(adder fieldAdder, ns string, val interface{}) {
   367  	var mapStrStr map[string]interface{}
   368  	buf, _ := json.Marshal(val)
   369  	json.Unmarshal(buf, &mapStrStr)
   370  	if len(mapStrStr) == 0 {
   371  		// Need to take care when recursively encoding strings,
   372  		// else JSON will happily add " at every level...
   373  		rv := reflect.ValueOf(val)
   374  		if rv.Kind() == reflect.String {
   375  			adder(ns, strings.Trim(rv.String(), `"`))
   376  		} else {
   377  			adder(ns, val)
   378  		}
   379  	} else {
   380  		for k, v := range mapStrStr {
   381  			structToFields(adder, ns+"."+k, v)
   382  		}
   383  	}
   384  }