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 | } |
129 |
Members