Move to vendor

Signed-off-by: Olivier Gambier <olivier@docker.com>
This commit is contained in:
Olivier Gambier 2016-03-18 14:07:13 -07:00
parent c8d8e7e357
commit 77e69b9cf3
1268 changed files with 34 additions and 24 deletions

View file

@ -0,0 +1,6 @@
Adds stacktraces to errors in golang.
This was made to help build the Bugsnag notifier but can be used standalone if
you like to have stacktraces on errors.
See [Godoc](https://godoc.org/github.com/bugsnag/bugsnag-go/errors) for the API docs.

90
vendor/github.com/bugsnag/bugsnag-go/errors/error.go generated vendored Normal file
View file

@ -0,0 +1,90 @@
// Package errors provides errors that have stack-traces.
package errors
import (
"bytes"
"fmt"
"reflect"
"runtime"
)
// The maximum number of stackframes on any error.
var MaxStackDepth = 50
// Error is an error with an attached stacktrace. It can be used
// wherever the builtin error interface is expected.
type Error struct {
Err error
stack []uintptr
frames []StackFrame
}
// New makes an Error from the given value. If that value is already an
// error then it will be used directly, if not, it will be passed to
// fmt.Errorf("%v"). The skip parameter indicates how far up the stack
// to start the stacktrace. 0 is from the current call, 1 from its caller, etc.
func New(e interface{}, skip int) *Error {
var err error
switch e := e.(type) {
case *Error:
return e
case error:
err = e
default:
err = fmt.Errorf("%v", e)
}
stack := make([]uintptr, MaxStackDepth)
length := runtime.Callers(2+skip, stack[:])
return &Error{
Err: err,
stack: stack[:length],
}
}
// Errorf creates a new error with the given message. You can use it
// as a drop-in replacement for fmt.Errorf() to provide descriptive
// errors in return values.
func Errorf(format string, a ...interface{}) *Error {
return New(fmt.Errorf(format, a...), 1)
}
// Error returns the underlying error's message.
func (err *Error) Error() string {
return err.Err.Error()
}
// Stack returns the callstack formatted the same way that go does
// in runtime/debug.Stack()
func (err *Error) Stack() []byte {
buf := bytes.Buffer{}
for _, frame := range err.StackFrames() {
buf.WriteString(frame.String())
}
return buf.Bytes()
}
// StackFrames returns an array of frames containing information about the
// stack.
func (err *Error) StackFrames() []StackFrame {
if err.frames == nil {
err.frames = make([]StackFrame, len(err.stack))
for i, pc := range err.stack {
err.frames[i] = NewStackFrame(pc)
}
}
return err.frames
}
// TypeName returns the type this error. e.g. *errors.stringError.
func (err *Error) TypeName() string {
if _, ok := err.Err.(uncaughtPanic); ok {
return "panic"
}
return reflect.TypeOf(err.Err).String()
}

View file

@ -0,0 +1,117 @@
package errors
import (
"bytes"
"fmt"
"io"
"runtime/debug"
"testing"
)
func TestStackFormatMatches(t *testing.T) {
defer func() {
err := recover()
if err != 'a' {
t.Fatal(err)
}
bs := [][]byte{Errorf("hi").Stack(), debug.Stack()}
// Ignore the first line (as it contains the PC of the .Stack() call)
bs[0] = bytes.SplitN(bs[0], []byte("\n"), 2)[1]
bs[1] = bytes.SplitN(bs[1], []byte("\n"), 2)[1]
if bytes.Compare(bs[0], bs[1]) != 0 {
t.Errorf("Stack didn't match")
t.Errorf("%s", bs[0])
t.Errorf("%s", bs[1])
}
}()
a()
}
func TestSkipWorks(t *testing.T) {
defer func() {
err := recover()
if err != 'a' {
t.Fatal(err)
}
bs := [][]byte{New("hi", 2).Stack(), debug.Stack()}
// should skip four lines of debug.Stack()
bs[1] = bytes.SplitN(bs[1], []byte("\n"), 5)[4]
if bytes.Compare(bs[0], bs[1]) != 0 {
t.Errorf("Stack didn't match")
t.Errorf("%s", bs[0])
t.Errorf("%s", bs[1])
}
}()
a()
}
func TestNewError(t *testing.T) {
e := func() error {
return New("hi", 1)
}()
if e.Error() != "hi" {
t.Errorf("Constructor with a string failed")
}
if New(fmt.Errorf("yo"), 0).Error() != "yo" {
t.Errorf("Constructor with an error failed")
}
if New(e, 0) != e {
t.Errorf("Constructor with an Error failed")
}
if New(nil, 0).Error() != "<nil>" {
t.Errorf("Constructor with nil failed")
}
}
func ExampleErrorf(x int) (int, error) {
if x%2 == 1 {
return 0, Errorf("can only halve even numbers, got %d", x)
}
return x / 2, nil
}
func ExampleNewError() (error, error) {
// Wrap io.EOF with the current stack-trace and return it
return nil, New(io.EOF, 0)
}
func ExampleNewError_skip() {
defer func() {
if err := recover(); err != nil {
// skip 1 frame (the deferred function) and then return the wrapped err
err = New(err, 1)
}
}()
}
func ExampleError_Stack(err Error) {
fmt.Printf("Error: %s\n%s", err.Error(), err.Stack())
}
func a() error {
b(5)
return nil
}
func b(i int) {
c()
}
func c() {
panic('a')
}

View file

@ -0,0 +1,127 @@
package errors
import (
"strconv"
"strings"
)
type uncaughtPanic struct{ message string }
func (p uncaughtPanic) Error() string {
return p.message
}
// ParsePanic allows you to get an error object from the output of a go program
// that panicked. This is particularly useful with https://github.com/mitchellh/panicwrap.
func ParsePanic(text string) (*Error, error) {
lines := strings.Split(text, "\n")
state := "start"
var message string
var stack []StackFrame
for i := 0; i < len(lines); i++ {
line := lines[i]
if state == "start" {
if strings.HasPrefix(line, "panic: ") {
message = strings.TrimPrefix(line, "panic: ")
state = "seek"
} else {
return nil, Errorf("bugsnag.panicParser: Invalid line (no prefix): %s", line)
}
} else if state == "seek" {
if strings.HasPrefix(line, "goroutine ") && strings.HasSuffix(line, "[running]:") {
state = "parsing"
}
} else if state == "parsing" {
if line == "" {
state = "done"
break
}
createdBy := false
if strings.HasPrefix(line, "created by ") {
line = strings.TrimPrefix(line, "created by ")
createdBy = true
}
i++
if i >= len(lines) {
return nil, Errorf("bugsnag.panicParser: Invalid line (unpaired): %s", line)
}
frame, err := parsePanicFrame(line, lines[i], createdBy)
if err != nil {
return nil, err
}
stack = append(stack, *frame)
if createdBy {
state = "done"
break
}
}
}
if state == "done" || state == "parsing" {
return &Error{Err: uncaughtPanic{message}, frames: stack}, nil
}
return nil, Errorf("could not parse panic: %v", text)
}
// The lines we're passing look like this:
//
// main.(*foo).destruct(0xc208067e98)
// /0/go/src/github.com/bugsnag/bugsnag-go/pan/main.go:22 +0x151
func parsePanicFrame(name string, line string, createdBy bool) (*StackFrame, error) {
idx := strings.LastIndex(name, "(")
if idx == -1 && !createdBy {
return nil, Errorf("bugsnag.panicParser: Invalid line (no call): %s", name)
}
if idx != -1 {
name = name[:idx]
}
pkg := ""
if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
pkg += name[:lastslash] + "/"
name = name[lastslash+1:]
}
if period := strings.Index(name, "."); period >= 0 {
pkg += name[:period]
name = name[period+1:]
}
name = strings.Replace(name, "·", ".", -1)
if !strings.HasPrefix(line, "\t") {
return nil, Errorf("bugsnag.panicParser: Invalid line (no tab): %s", line)
}
idx = strings.LastIndex(line, ":")
if idx == -1 {
return nil, Errorf("bugsnag.panicParser: Invalid line (no line number): %s", line)
}
file := line[1:idx]
number := line[idx+1:]
if idx = strings.Index(number, " +"); idx > -1 {
number = number[:idx]
}
lno, err := strconv.ParseInt(number, 10, 32)
if err != nil {
return nil, Errorf("bugsnag.panicParser: Invalid line (bad line number): %s", line)
}
return &StackFrame{
File: file,
LineNumber: int(lno),
Package: pkg,
Name: name,
}, nil
}

View file

@ -0,0 +1,142 @@
package errors
import (
"reflect"
"testing"
)
var createdBy = `panic: hello!
goroutine 54 [running]:
runtime.panic(0x35ce40, 0xc208039db0)
/0/c/go/src/pkg/runtime/panic.c:279 +0xf5
github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.func·001()
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:13 +0x74
net/http.(*Server).Serve(0xc20806c780, 0x910c88, 0xc20803e168, 0x0, 0x0)
/0/c/go/src/pkg/net/http/server.go:1698 +0x91
created by github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.App.Index
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:14 +0x3e
goroutine 16 [IO wait]:
net.runtime_pollWait(0x911c30, 0x72, 0x0)
/0/c/go/src/pkg/runtime/netpoll.goc:146 +0x66
net.(*pollDesc).Wait(0xc2080ba990, 0x72, 0x0, 0x0)
/0/c/go/src/pkg/net/fd_poll_runtime.go:84 +0x46
net.(*pollDesc).WaitRead(0xc2080ba990, 0x0, 0x0)
/0/c/go/src/pkg/net/fd_poll_runtime.go:89 +0x42
net.(*netFD).accept(0xc2080ba930, 0x58be30, 0x0, 0x9103f0, 0x23)
/0/c/go/src/pkg/net/fd_unix.go:409 +0x343
net.(*TCPListener).AcceptTCP(0xc20803e168, 0x8, 0x0, 0x0)
/0/c/go/src/pkg/net/tcpsock_posix.go:234 +0x5d
net.(*TCPListener).Accept(0xc20803e168, 0x0, 0x0, 0x0, 0x0)
/0/c/go/src/pkg/net/tcpsock_posix.go:244 +0x4b
github.com/revel/revel.Run(0xe6d9)
/0/go/src/github.com/revel/revel/server.go:113 +0x926
main.main()
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/tmp/main.go:109 +0xe1a
`
var normalSplit = `panic: hello!
goroutine 54 [running]:
runtime.panic(0x35ce40, 0xc208039db0)
/0/c/go/src/pkg/runtime/panic.c:279 +0xf5
github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.func·001()
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:13 +0x74
net/http.(*Server).Serve(0xc20806c780, 0x910c88, 0xc20803e168, 0x0, 0x0)
/0/c/go/src/pkg/net/http/server.go:1698 +0x91
goroutine 16 [IO wait]:
net.runtime_pollWait(0x911c30, 0x72, 0x0)
/0/c/go/src/pkg/runtime/netpoll.goc:146 +0x66
net.(*pollDesc).Wait(0xc2080ba990, 0x72, 0x0, 0x0)
/0/c/go/src/pkg/net/fd_poll_runtime.go:84 +0x46
net.(*pollDesc).WaitRead(0xc2080ba990, 0x0, 0x0)
/0/c/go/src/pkg/net/fd_poll_runtime.go:89 +0x42
net.(*netFD).accept(0xc2080ba930, 0x58be30, 0x0, 0x9103f0, 0x23)
/0/c/go/src/pkg/net/fd_unix.go:409 +0x343
net.(*TCPListener).AcceptTCP(0xc20803e168, 0x8, 0x0, 0x0)
/0/c/go/src/pkg/net/tcpsock_posix.go:234 +0x5d
net.(*TCPListener).Accept(0xc20803e168, 0x0, 0x0, 0x0, 0x0)
/0/c/go/src/pkg/net/tcpsock_posix.go:244 +0x4b
github.com/revel/revel.Run(0xe6d9)
/0/go/src/github.com/revel/revel/server.go:113 +0x926
main.main()
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/tmp/main.go:109 +0xe1a
`
var lastGoroutine = `panic: hello!
goroutine 16 [IO wait]:
net.runtime_pollWait(0x911c30, 0x72, 0x0)
/0/c/go/src/pkg/runtime/netpoll.goc:146 +0x66
net.(*pollDesc).Wait(0xc2080ba990, 0x72, 0x0, 0x0)
/0/c/go/src/pkg/net/fd_poll_runtime.go:84 +0x46
net.(*pollDesc).WaitRead(0xc2080ba990, 0x0, 0x0)
/0/c/go/src/pkg/net/fd_poll_runtime.go:89 +0x42
net.(*netFD).accept(0xc2080ba930, 0x58be30, 0x0, 0x9103f0, 0x23)
/0/c/go/src/pkg/net/fd_unix.go:409 +0x343
net.(*TCPListener).AcceptTCP(0xc20803e168, 0x8, 0x0, 0x0)
/0/c/go/src/pkg/net/tcpsock_posix.go:234 +0x5d
net.(*TCPListener).Accept(0xc20803e168, 0x0, 0x0, 0x0, 0x0)
/0/c/go/src/pkg/net/tcpsock_posix.go:244 +0x4b
github.com/revel/revel.Run(0xe6d9)
/0/go/src/github.com/revel/revel/server.go:113 +0x926
main.main()
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/tmp/main.go:109 +0xe1a
goroutine 54 [running]:
runtime.panic(0x35ce40, 0xc208039db0)
/0/c/go/src/pkg/runtime/panic.c:279 +0xf5
github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.func·001()
/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:13 +0x74
net/http.(*Server).Serve(0xc20806c780, 0x910c88, 0xc20803e168, 0x0, 0x0)
/0/c/go/src/pkg/net/http/server.go:1698 +0x91
`
var result = []StackFrame{
StackFrame{File: "/0/c/go/src/pkg/runtime/panic.c", LineNumber: 279, Name: "panic", Package: "runtime"},
StackFrame{File: "/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go", LineNumber: 13, Name: "func.001", Package: "github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers"},
StackFrame{File: "/0/c/go/src/pkg/net/http/server.go", LineNumber: 1698, Name: "(*Server).Serve", Package: "net/http"},
}
var resultCreatedBy = append(result,
StackFrame{File: "/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go", LineNumber: 14, Name: "App.Index", Package: "github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers", ProgramCounter: 0x0})
func TestParsePanic(t *testing.T) {
todo := map[string]string{
"createdBy": createdBy,
"normalSplit": normalSplit,
"lastGoroutine": lastGoroutine,
}
for key, val := range todo {
Err, err := ParsePanic(val)
if err != nil {
t.Fatal(err)
}
if Err.TypeName() != "panic" {
t.Errorf("Wrong type: %s", Err.TypeName())
}
if Err.Error() != "hello!" {
t.Errorf("Wrong message: %s", Err.TypeName())
}
if Err.StackFrames()[0].Func() != nil {
t.Errorf("Somehow managed to find a func...")
}
result := result
if key == "createdBy" {
result = resultCreatedBy
}
if !reflect.DeepEqual(Err.StackFrames(), result) {
t.Errorf("Wrong stack for %s: %#v", key, Err.StackFrames())
}
}
}

View file

@ -0,0 +1,97 @@
package errors
import (
"bytes"
"fmt"
"io/ioutil"
"runtime"
"strings"
)
// A StackFrame contains all necessary information about to generate a line
// in a callstack.
type StackFrame struct {
File string
LineNumber int
Name string
Package string
ProgramCounter uintptr
}
// NewStackFrame popoulates a stack frame object from the program counter.
func NewStackFrame(pc uintptr) (frame StackFrame) {
frame = StackFrame{ProgramCounter: pc}
if frame.Func() == nil {
return
}
frame.Package, frame.Name = packageAndName(frame.Func())
// pc -1 because the program counters we use are usually return addresses,
// and we want to show the line that corresponds to the function call
frame.File, frame.LineNumber = frame.Func().FileLine(pc - 1)
return
}
// Func returns the function that this stackframe corresponds to
func (frame *StackFrame) Func() *runtime.Func {
if frame.ProgramCounter == 0 {
return nil
}
return runtime.FuncForPC(frame.ProgramCounter)
}
// String returns the stackframe formatted in the same way as go does
// in runtime/debug.Stack()
func (frame *StackFrame) String() string {
str := fmt.Sprintf("%s:%d (0x%x)\n", frame.File, frame.LineNumber, frame.ProgramCounter)
source, err := frame.SourceLine()
if err != nil {
return str
}
return str + fmt.Sprintf("\t%s: %s\n", frame.Name, source)
}
// SourceLine gets the line of code (from File and Line) of the original source if possible
func (frame *StackFrame) SourceLine() (string, error) {
data, err := ioutil.ReadFile(frame.File)
if err != nil {
return "", err
}
lines := bytes.Split(data, []byte{'\n'})
if frame.LineNumber <= 0 || frame.LineNumber >= len(lines) {
return "???", nil
}
// -1 because line-numbers are 1 based, but our array is 0 based
return string(bytes.Trim(lines[frame.LineNumber-1], " \t")), nil
}
func packageAndName(fn *runtime.Func) (string, string) {
name := fn.Name()
pkg := ""
// The name includes the path name to the package, which is unnecessary
// since the file name is already included. Plus, it has center dots.
// That is, we see
// runtime/debug.*T·ptrmethod
// and want
// *T.ptrmethod
// Since the package path might contains dots (e.g. code.google.com/...),
// we first remove the path prefix if there is one.
if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
pkg += name[:lastslash] + "/"
name = name[lastslash+1:]
}
if period := strings.Index(name, "."); period >= 0 {
pkg += name[:period]
name = name[period+1:]
}
name = strings.Replace(name, "·", ".", -1)
return pkg, name
}