golang.org/x/tools/gopls@v0.15.3/internal/util/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 "time" 21 22 "golang.org/x/tools/gopls/internal/telemetry" 23 ) 24 25 // PanicOnBugs controls whether to panic when bugs are reported. 26 // 27 // It may be set to true during testing. 28 // 29 // TODO(adonovan): should we make the default true, and 30 // suppress it only in the product (gopls/main.go)? 31 var PanicOnBugs = false 32 33 var ( 34 mu sync.Mutex 35 exemplars map[string]Bug 36 handlers []func(Bug) 37 ) 38 39 // A Bug represents an unexpected event or broken invariant. They are used for 40 // capturing metadata that helps us understand the event. 41 // 42 // Bugs are JSON-serializable. 43 type Bug struct { 44 File string // file containing the call to bug.Report 45 Line int // line containing the call to bug.Report 46 Description string // description of the bug 47 Key string // key identifying the bug (file:line if available) 48 Stack string // call stack 49 AtTime time.Time // time the bug was reported 50 } 51 52 // Reportf reports a formatted bug message. 53 func Reportf(format string, args ...interface{}) { 54 report(fmt.Sprintf(format, args...)) 55 } 56 57 // Errorf calls fmt.Errorf for the given arguments, and reports the resulting 58 // error message as a bug. 59 func Errorf(format string, args ...interface{}) error { 60 err := fmt.Errorf(format, args...) 61 report(err.Error()) 62 return err 63 } 64 65 // Report records a new bug encountered on the server. 66 // It uses reflection to report the position of the immediate caller. 67 func Report(description string) { 68 report(description) 69 } 70 71 // BugReportCount is a telemetry counter that tracks # of bug reports. 72 var BugReportCount = telemetry.NewStackCounter("gopls/bug", 16) 73 74 func report(description string) { 75 _, file, line, ok := runtime.Caller(2) // all exported reporting functions call report directly 76 77 key := "<missing callsite>" 78 if ok { 79 key = fmt.Sprintf("%s:%d", file, line) 80 } 81 82 if PanicOnBugs { 83 panic(fmt.Sprintf("%s: %s", key, description)) 84 } 85 86 bug := Bug{ 87 File: file, 88 Line: line, 89 Description: description, 90 Key: key, 91 Stack: string(debug.Stack()), 92 AtTime: time.Now(), 93 } 94 95 newBug := false 96 mu.Lock() 97 if _, ok := exemplars[key]; !ok { 98 if exemplars == nil { 99 exemplars = make(map[string]Bug) 100 } 101 exemplars[key] = bug // capture one exemplar per key 102 newBug = true 103 } 104 hh := handlers 105 handlers = nil 106 mu.Unlock() 107 108 if newBug { 109 BugReportCount.Inc() 110 } 111 // Call the handlers outside the critical section since a 112 // handler may itself fail and call bug.Report. Since handlers 113 // are one-shot, the inner call should be trivial. 114 for _, handle := range hh { 115 handle(bug) 116 } 117 } 118 119 // Handle adds a handler function that will be called with the next 120 // bug to occur on the server. The handler only ever receives one bug. 121 // It is called synchronously, and should return in a timely manner. 122 func Handle(h func(Bug)) { 123 mu.Lock() 124 defer mu.Unlock() 125 handlers = append(handlers, h) 126 } 127 128 // List returns a slice of bug exemplars -- the first bugs to occur at each 129 // callsite. 130 func List() []Bug { 131 mu.Lock() 132 defer mu.Unlock() 133 134 var bugs []Bug 135 136 for _, bug := range exemplars { 137 bugs = append(bugs, bug) 138 } 139 140 sort.Slice(bugs, func(i, j int) bool { 141 return bugs[i].Key < bugs[j].Key 142 }) 143 144 return bugs 145 }