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  }