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