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
|
// 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"
)
// PanicOnBugs controls whether to panic when bugs are reported.
//
// It may be set to true during testing.
var PanicOnBugs = false
var (
mu sync.Mutex
exemplars map[string]Bug
waiters []chan<- Bug
)
// A Bug represents an unexpected event or broken invariant. They are used for
// capturing metadata that helps us understand the event.
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
Data Data // additional metadata
Key string // key identifying the bug (file:line if available)
Stack string // call stack
}
// Data is additional metadata to record for a bug.
type Data map[string]interface{}
// Reportf reports a formatted bug message.
func Reportf(format string, args ...interface{}) {
report(fmt.Sprintf(format, args...), nil)
}
// 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(), nil)
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, data Data) {
report(description, data)
}
func report(description string, data Data) {
_, 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,
Data: data,
Key: key,
Stack: string(debug.Stack()),
}
mu.Lock()
defer mu.Unlock()
if exemplars == nil {
exemplars = make(map[string]Bug)
}
if _, ok := exemplars[key]; !ok {
exemplars[key] = bug // capture one exemplar per key
}
for _, waiter := range waiters {
waiter <- bug
}
waiters = nil
}
// Notify returns a channel that will be sent the next bug to occur on the
// server. This channel only ever receives one bug.
func Notify() <-chan Bug {
mu.Lock()
defer mu.Unlock()
ch := make(chan Bug, 1) // 1-buffered so that bug reporting is non-blocking
waiters = append(waiters, ch)
return ch
}
// 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
}
|