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 }