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  }