github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/internal/bug/bug.go (about) 1 // Copyright 2022 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package bug provides utilities for reporting internal bugs, and being 6 // notified when they occur. 7 // 8 // Philosophically, because gopls runs as a sidecar process that the user does 9 // not directly control, sometimes it keeps going on broken invariants rather 10 // than panicking. In those cases, bug reports provide a mechanism to alert 11 // developers and capture relevant metadata. 12 package bug 13 14 import ( 15 "fmt" 16 "runtime" 17 "runtime/debug" 18 "sort" 19 "sync" 20 ) 21 22 // PanicOnBugs controls whether to panic when bugs are reported. 23 // 24 // It may be set to true during testing. 25 var PanicOnBugs = false 26 27 var ( 28 mu sync.Mutex 29 exemplars map[string]Bug 30 waiters []chan<- Bug 31 ) 32 33 // A Bug represents an unexpected event or broken invariant. They are used for 34 // capturing metadata that helps us understand the event. 35 type Bug struct { 36 File string // file containing the call to bug.Report 37 Line int // line containing the call to bug.Report 38 Description string // description of the bug 39 Data Data // additional metadata 40 Key string // key identifying the bug (file:line if available) 41 Stack string // call stack 42 } 43 44 // Data is additional metadata to record for a bug. 45 type Data map[string]interface{} 46 47 // Reportf reports a formatted bug message. 48 func Reportf(format string, args ...interface{}) { 49 Report(fmt.Sprintf(format, args...), nil) 50 } 51 52 // Errorf calls fmt.Errorf for the given arguments, and reports the resulting 53 // error message as a bug. 54 func Errorf(format string, args ...interface{}) error { 55 err := fmt.Errorf(format, args...) 56 Report(err.Error(), nil) 57 return err 58 } 59 60 // Report records a new bug encountered on the server. 61 // It uses reflection to report the position of the immediate caller. 62 func Report(description string, data Data) { 63 _, file, line, ok := runtime.Caller(1) 64 65 key := "<missing callsite>" 66 if ok { 67 key = fmt.Sprintf("%s:%d", file, line) 68 } 69 70 if PanicOnBugs { 71 panic(fmt.Sprintf("%s: %s", key, description)) 72 } 73 74 bug := Bug{ 75 File: file, 76 Line: line, 77 Description: description, 78 Data: data, 79 Key: key, 80 Stack: string(debug.Stack()), 81 } 82 83 mu.Lock() 84 defer mu.Unlock() 85 86 if exemplars == nil { 87 exemplars = make(map[string]Bug) 88 } 89 90 if _, ok := exemplars[key]; !ok { 91 exemplars[key] = bug // capture one exemplar per key 92 } 93 94 for _, waiter := range waiters { 95 waiter <- bug 96 } 97 waiters = nil 98 } 99 100 // Notify returns a channel that will be sent the next bug to occur on the 101 // server. This channel only ever receives one bug. 102 func Notify() <-chan Bug { 103 mu.Lock() 104 defer mu.Unlock() 105 106 ch := make(chan Bug, 1) // 1-buffered so that bug reporting is non-blocking 107 waiters = append(waiters, ch) 108 return ch 109 } 110 111 // List returns a slice of bug exemplars -- the first bugs to occur at each 112 // callsite. 113 func List() []Bug { 114 mu.Lock() 115 defer mu.Unlock() 116 117 var bugs []Bug 118 119 for _, bug := range exemplars { 120 bugs = append(bugs, bug) 121 } 122 123 sort.Slice(bugs, func(i, j int) bool { 124 return bugs[i].Key < bugs[j].Key 125 }) 126 127 return bugs 128 }