diff options
author | Kali Kaneko (leap communications) <kali@leap.se> | 2019-01-12 18:39:45 +0100 |
---|---|---|
committer | Ruben Pollan <meskio@sindominio.net> | 2019-01-17 12:30:32 +0100 |
commit | b1247d2d0d51108c910a73891ff3116e5f032ab1 (patch) | |
tree | e9948964f0bfb1ad2df3bc7bad02aa1f41ccfbd8 /vendor/github.com/getlantern/errors | |
parent | efcb8312e31b5c2261b1a1e95ace55b322cfcc27 (diff) |
[pkg] all your deps are vendored to us
Diffstat (limited to 'vendor/github.com/getlantern/errors')
-rw-r--r-- | vendor/github.com/getlantern/errors/errors.go | 566 | ||||
-rw-r--r-- | vendor/github.com/getlantern/errors/errors_test.go | 142 | ||||
-rw-r--r-- | vendor/github.com/getlantern/errors/hide.go | 50 |
3 files changed, 758 insertions, 0 deletions
diff --git a/vendor/github.com/getlantern/errors/errors.go b/vendor/github.com/getlantern/errors/errors.go new file mode 100644 index 0000000..8b2b84f --- /dev/null +++ b/vendor/github.com/getlantern/errors/errors.go @@ -0,0 +1,566 @@ +/* +Package errors defines error types used across Lantern project. + + n, err := Foo() + if err != nil { + return n, errors.New("Unable to do Foo: %v", err) + } + +or + + n, err := Foo() + return n, errors.Wrap(err) + +New() method will create a new error with err as its cause. Wrap will wrap err, +returning nil if err is nil. If err is an error from Go's standard library, +errors will extract details from that error, at least the Go type name and the +return value of err.Error(). + +One can record the operation on which the error occurred using Op(): + + return n, errors.New("Unable to do Foo: %v", err).Op("FooDooer") + +One can also record additional data: + + return n, errors. + New("Unable to do Foo: %v", err). + Op("FooDooer"). + With("mydata", "myvalue"). + With("moredata", 5) + +When used with github.com/getlantern/ops, Error captures its current context +and propagates that data for use in calling layers. + +When used with github.com/getlantern/golog, Error provides stacktraces: + + Hello World + at github.com/getlantern/errors.TestNewWithCause (errors_test.go:999) + at testing.tRunner (testing.go:999) + at runtime.goexit (asm_amd999.s:999) + Caused by: World + at github.com/getlantern/errors.buildCause (errors_test.go:999) + at github.com/getlantern/errors.TestNewWithCause (errors_test.go:999) + at testing.tRunner (testing.go:999) + at runtime.goexit (asm_amd999.s:999) + Caused by: orld + Caused by: ld + at github.com/getlantern/errors.buildSubSubCause (errors_test.go:999) + at github.com/getlantern/errors.buildSubCause (errors_test.go:999) + at github.com/getlantern/errors.buildCause (errors_test.go:999) + at github.com/getlantern/errors.TestNewWithCause (errors_test.go:999) + at testing.tRunner (testing.go:999) + at runtime.goexit (asm_amd999.s:999) + Caused by: d + +It's the caller's responsibility to avoid race conditions accessing the same +error instance from multiple goroutines. +*/ +package errors + +import ( + "bufio" + "bytes" + "crypto/tls" + "crypto/x509" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "net/textproto" + "net/url" + "os" + "os/exec" + "reflect" + "runtime" + "strconv" + "strings" + "syscall" + "time" + "unicode" + + "github.com/getlantern/context" + "github.com/getlantern/hidden" + "github.com/getlantern/ops" + "github.com/go-stack/stack" +) + +// Error wraps system and application defined errors in unified structure for +// reporting and logging. It's not meant to be created directly. User New(), +// Wrap() and Report() instead. +type Error interface { + error + context.Contextual + + // ErrorClean returns a non-parameterized version of the error whenever + // possible. For example, if the error text is: + // + // unable to dial www.google.com caused by: i/o timeout + // + // ErrorClean might return: + // + // unable to dial %v caused by: %v + // + // This can be useful when performing analytics on the error. + ErrorClean() string + + // MultiLinePrinter implements the interface golog.MultiLine + MultiLinePrinter() func(buf *bytes.Buffer) bool + + // Op attaches a hint of the operation triggers this Error. Many error types + // returned by net and os package have Op pre-filled. + Op(op string) Error + + // With attaches arbitrary field to the error. keys will be normalized as + // underscore_divided_words, so all characters except letters and numbers will + // be replaced with underscores, and all letters will be lowercased. + With(key string, value interface{}) Error + + // RootCause returns the bottom-most cause of this Error. If the Error + // resulted from wrapping a plain error, the wrapped error will be returned as + // the cause. + RootCause() error +} + +type structured struct { + id uint64 + hiddenID string + data context.Map + context context.Map + wrapped error + cause Error + callStack stack.CallStack +} + +// New creates an Error with supplied description and format arguments to the +// description. If any of the arguments is an error, we use that as the cause. +func New(desc string, args ...interface{}) Error { + return NewOffset(1, desc, args...) +} + +// NewOffset is like New but offsets the stack by the given offset. This is +// useful for utilities like golog that may create errors on behalf of others. +func NewOffset(offset int, desc string, args ...interface{}) Error { + var cause error + for _, arg := range args { + err, isError := arg.(error) + if isError { + cause = err + break + } + } + e := buildError(desc, fmt.Sprintf(desc, args...), nil, Wrap(cause)) + e.attachStack(2 + offset) + return e +} + +// Wrap creates an Error based on the information in an error instance. It +// returns nil if the error passed in is nil, so we can simply call +// errors.Wrap(s.l.Close()) regardless there's an error or not. If the error is +// already wrapped, it is returned as is. +func Wrap(err error) Error { + return wrapSkipFrames(err, 1) +} + +// Fill implements the method from the context.Contextual interface. +func (e *structured) Fill(m context.Map) { + if e != nil { + if e.cause != nil { + // Include data from cause, which supercedes context + e.cause.Fill(m) + } + // Include the context, which supercedes the cause + for key, value := range e.context { + m[key] = value + } + // Now include the error's data, which supercedes everything + for key, value := range e.data { + m[key] = value + } + } +} + +func (e *structured) Op(op string) Error { + e.data["error_op"] = op + return e +} + +func (e *structured) With(key string, value interface{}) Error { + parts := strings.FieldsFunc(key, func(c rune) bool { + return !unicode.IsLetter(c) && !unicode.IsNumber(c) + }) + k := strings.ToLower(strings.Join(parts, "_")) + if k == "error" || k == "error_op" { + // Never overwrite these + return e + } + switch actual := value.(type) { + case string, int, bool, time.Time: + e.data[k] = actual + default: + e.data[k] = fmt.Sprint(actual) + } + return e +} + +func (e *structured) RootCause() error { + if e.cause == nil { + if e.wrapped != nil { + return e.wrapped + } + return e + } + return e.cause.RootCause() +} + +func (e *structured) ErrorClean() string { + return e.data["error"].(string) +} + +// Error satisfies the error interface +func (e *structured) Error() string { + return e.data["error_text"].(string) + e.hiddenID +} + +func (e *structured) MultiLinePrinter() func(buf *bytes.Buffer) bool { + first := true + indent := false + err := e + stackPosition := 0 + switchedCause := false + return func(buf *bytes.Buffer) bool { + if indent { + buf.WriteString(" ") + } + if first { + buf.WriteString(e.Error()) + first = false + indent = true + return true + } + if switchedCause { + fmt.Fprintf(buf, "Caused by: %v", err) + if err.callStack != nil && len(err.callStack) > 0 { + switchedCause = false + indent = true + return true + } + if err.cause == nil { + return false + } + err = err.cause.(*structured) + return true + } + if stackPosition < len(err.callStack) { + buf.WriteString("at ") + call := err.callStack[stackPosition] + fmt.Fprintf(buf, "%+n (%s:%d)", call, call, call) + stackPosition++ + } + if stackPosition >= len(err.callStack) { + switch cause := err.cause.(type) { + case *structured: + err = cause + indent = false + stackPosition = 0 + switchedCause = true + default: + return false + } + } + return err != nil + } +} + +func wrapSkipFrames(err error, skip int) Error { + if err == nil { + return nil + } + + // Look for *structureds + if e, ok := err.(*structured); ok { + return e + } + + var cause Error + // Look for hidden *structureds + hiddenIDs, err2 := hidden.Extract(err.Error()) + if err2 == nil && len(hiddenIDs) > 0 { + // Take the first hidden ID as our cause + cause = get(hiddenIDs[0]) + } + + // Create a new *structured + return buildError("", "", err, cause) +} + +func (e *structured) attachStack(skip int) { + call := stack.Caller(skip) + e.callStack = stack.Trace().TrimBelow(call) + e.data["error_location"] = fmt.Sprintf("%+n (%s:%d)", call, call, call) +} + +func buildError(desc string, fullText string, wrapped error, cause Error) *structured { + e := &structured{ + data: make(context.Map), + // We capture the current context to allow it to propagate to higher layers. + context: ops.AsMap(nil, false), + wrapped: wrapped, + cause: cause, + } + e.save() + + errorType := "errors.Error" + if wrapped != nil { + op, goType, wrappedDesc, extra := parseError(wrapped) + if desc == "" { + desc = wrappedDesc + } + e.Op(op) + errorType = goType + if extra != nil { + for key, value := range extra { + e.data[key] = value + } + } + } + + cleanedDesc := hidden.Clean(desc) + e.data["error"] = cleanedDesc + if fullText != "" { + e.data["error_text"] = hidden.Clean(fullText) + } else { + e.data["error_text"] = cleanedDesc + } + e.data["error_type"] = errorType + + return e +} + +func parseError(err error) (op string, goType string, desc string, extra map[string]string) { + extra = make(map[string]string) + + // interfaces + if _, ok := err.(net.Error); ok { + if opError, ok := err.(*net.OpError); ok { + op = opError.Op + if opError.Source != nil { + extra["remote_addr"] = opError.Source.String() + } + if opError.Addr != nil { + extra["local_addr"] = opError.Addr.String() + } + extra["network"] = opError.Net + err = opError.Err + } + switch actual := err.(type) { + case *net.AddrError: + goType = "net.AddrError" + desc = actual.Err + extra["addr"] = actual.Addr + case *net.DNSError: + goType = "net.DNSError" + desc = actual.Err + extra["domain"] = actual.Name + if actual.Server != "" { + extra["dns_server"] = actual.Server + } + case *net.InvalidAddrError: + goType = "net.InvalidAddrError" + desc = actual.Error() + case *net.ParseError: + goType = "net.ParseError" + desc = "invalid " + actual.Type + extra["text_to_parse"] = actual.Text + case net.UnknownNetworkError: + goType = "net.UnknownNetworkError" + desc = "unknown network" + case syscall.Errno: + goType = "syscall.Errno" + desc = actual.Error() + case *url.Error: + goType = "url.Error" + desc = actual.Err.Error() + op = actual.Op + default: + goType = reflect.TypeOf(err).String() + desc = err.Error() + } + return + } + if _, ok := err.(runtime.Error); ok { + desc = err.Error() + switch err.(type) { + case *runtime.TypeAssertionError: + goType = "runtime.TypeAssertionError" + default: + goType = reflect.TypeOf(err).String() + } + return + } + + // structs + switch actual := err.(type) { + case *http.ProtocolError: + desc = actual.ErrorString + if name, ok := httpProtocolErrors[err]; ok { + goType = name + } else { + goType = "http.ProtocolError" + } + case url.EscapeError, *url.EscapeError: + goType = "url.EscapeError" + desc = "invalid URL escape" + case url.InvalidHostError, *url.InvalidHostError: + goType = "url.InvalidHostError" + desc = "invalid character in host name" + case *textproto.Error: + goType = "textproto.Error" + desc = actual.Error() + case textproto.ProtocolError, *textproto.ProtocolError: + goType = "textproto.ProtocolError" + desc = actual.Error() + + case tls.RecordHeaderError: + goType = "tls.RecordHeaderError" + desc = actual.Msg + extra["header"] = hex.EncodeToString(actual.RecordHeader[:]) + case x509.CertificateInvalidError: + goType = "x509.CertificateInvalidError" + desc = actual.Error() + case x509.ConstraintViolationError: + goType = "x509.ConstraintViolationError" + desc = actual.Error() + case x509.HostnameError: + goType = "x509.HostnameError" + desc = actual.Error() + extra["host"] = actual.Host + case x509.InsecureAlgorithmError: + goType = "x509.InsecureAlgorithmError" + desc = actual.Error() + case x509.SystemRootsError: + goType = "x509.SystemRootsError" + desc = actual.Error() + case x509.UnhandledCriticalExtension: + goType = "x509.UnhandledCriticalExtension" + desc = actual.Error() + case x509.UnknownAuthorityError: + goType = "x509.UnknownAuthorityError" + desc = actual.Error() + case hex.InvalidByteError: + goType = "hex.InvalidByteError" + desc = "invalid byte" + case *json.InvalidUTF8Error: + goType = "json.InvalidUTF8Error" + desc = "invalid UTF-8 in string" + case *json.InvalidUnmarshalError: + goType = "json.InvalidUnmarshalError" + desc = actual.Error() + case *json.MarshalerError: + goType = "json.MarshalerError" + desc = actual.Error() + case *json.SyntaxError: + goType = "json.SyntaxError" + desc = actual.Error() + case *json.UnmarshalFieldError: + goType = "json.UnmarshalFieldError" + desc = actual.Error() + case *json.UnmarshalTypeError: + goType = "json.UnmarshalTypeError" + desc = actual.Error() + case *json.UnsupportedTypeError: + goType = "json.UnsupportedTypeError" + desc = actual.Error() + case *json.UnsupportedValueError: + goType = "json.UnsupportedValueError" + desc = actual.Error() + + case *os.LinkError: + goType = "os.LinkError" + desc = actual.Error() + case *os.PathError: + goType = "os.PathError" + op = actual.Op + desc = actual.Err.Error() + case *os.SyscallError: + goType = "os.SyscallError" + op = actual.Syscall + desc = actual.Err.Error() + case *exec.Error: + goType = "exec.Error" + desc = actual.Err.Error() + case *exec.ExitError: + goType = "exec.ExitError" + desc = actual.Error() + // TODO: limit the length + extra["stderr"] = string(actual.Stderr) + case *strconv.NumError: + goType = "strconv.NumError" + desc = actual.Err.Error() + extra["function"] = actual.Func + case *time.ParseError: + goType = "time.ParseError" + desc = actual.Message + default: + desc = err.Error() + if t, ok := miscErrors[err]; ok { + goType = t + return + } + goType = reflect.TypeOf(err).String() + } + return +} + +var httpProtocolErrors = map[error]string{ + http.ErrHeaderTooLong: "http.ErrHeaderTooLong", + http.ErrShortBody: "http.ErrShortBody", + http.ErrNotSupported: "http.ErrNotSupported", + http.ErrUnexpectedTrailer: "http.ErrUnexpectedTrailer", + http.ErrMissingContentLength: "http.ErrMissingContentLength", + http.ErrNotMultipart: "http.ErrNotMultipart", + http.ErrMissingBoundary: "http.ErrMissingBoundary", +} + +var miscErrors = map[error]string{ + bufio.ErrInvalidUnreadByte: "bufio.ErrInvalidUnreadByte", + bufio.ErrInvalidUnreadRune: "bufio.ErrInvalidUnreadRune", + bufio.ErrBufferFull: "bufio.ErrBufferFull", + bufio.ErrNegativeCount: "bufio.ErrNegativeCount", + bufio.ErrTooLong: "bufio.ErrTooLong", + bufio.ErrNegativeAdvance: "bufio.ErrNegativeAdvance", + bufio.ErrAdvanceTooFar: "bufio.ErrAdvanceTooFar", + bufio.ErrFinalToken: "bufio.ErrFinalToken", + + http.ErrWriteAfterFlush: "http.ErrWriteAfterFlush", + http.ErrBodyNotAllowed: "http.ErrBodyNotAllowed", + http.ErrHijacked: "http.ErrHijacked", + http.ErrContentLength: "http.ErrContentLength", + http.ErrBodyReadAfterClose: "http.ErrBodyReadAfterClose", + http.ErrHandlerTimeout: "http.ErrHandlerTimeout", + http.ErrLineTooLong: "http.ErrLineTooLong", + http.ErrMissingFile: "http.ErrMissingFile", + http.ErrNoCookie: "http.ErrNoCookie", + http.ErrNoLocation: "http.ErrNoLocation", + http.ErrSkipAltProtocol: "http.ErrSkipAltProtocol", + + io.EOF: "io.EOF", + io.ErrClosedPipe: "io.ErrClosedPipe", + io.ErrNoProgress: "io.ErrNoProgress", + io.ErrShortBuffer: "io.ErrShortBuffer", + io.ErrShortWrite: "io.ErrShortWrite", + io.ErrUnexpectedEOF: "io.ErrUnexpectedEOF", + + os.ErrInvalid: "os.ErrInvalid", + os.ErrPermission: "os.ErrPermission", + os.ErrExist: "os.ErrExist", + os.ErrNotExist: "os.ErrNotExist", + + exec.ErrNotFound: "exec.ErrNotFound", + + x509.ErrUnsupportedAlgorithm: "x509.ErrUnsupportedAlgorithm", + x509.IncorrectPasswordError: "x509.IncorrectPasswordError", + + hex.ErrLength: "hex.ErrLength", +} diff --git a/vendor/github.com/getlantern/errors/errors_test.go b/vendor/github.com/getlantern/errors/errors_test.go new file mode 100644 index 0000000..7c4887a --- /dev/null +++ b/vendor/github.com/getlantern/errors/errors_test.go @@ -0,0 +1,142 @@ +package errors + +import ( + "bytes" + "fmt" + "regexp" + "testing" + + "github.com/getlantern/context" + "github.com/getlantern/hidden" + "github.com/getlantern/ops" + "github.com/stretchr/testify/assert" +) + +var ( + replaceNumbers = regexp.MustCompile("[0-9]+") +) + +func TestFull(t *testing.T) { + var firstErr Error + + // Iterate past the size of the hidden buffer + for i := 0; i < len(hiddenErrors)*2; i++ { + op := ops.Begin("op1").Set("ca", 100).Set("cd", 100) + e := New("Hello %v", "There").Op("My Op").With("DaTa_1", 1) + op.End() + if firstErr == nil { + firstErr = e + } + assert.Equal(t, "Hello There", e.Error()[:11]) + op = ops.Begin("op2").Set("ca", 200).Set("cb", 200).Set("cc", 200) + e3 := Wrap(fmt.Errorf("I'm wrapping your text: %v", e)).Op("outer op").With("dATA+1", i).With("cb", 300) + op.End() + assert.Equal(t, e, e3.(*structured).cause, "Wrapping a regular error should have extracted the contained *Error") + m := make(context.Map) + e3.Fill(m) + assert.Equal(t, i, m["data_1"], "Error's data should dominate all") + assert.Equal(t, 200, m["ca"], "Error's context should dominate cause") + assert.Equal(t, 300, m["cb"], "Error's data should dominate its context") + assert.Equal(t, 200, m["cc"], "Error's context should come through") + assert.Equal(t, 100, m["cd"], "Cause's context should come through") + assert.Equal(t, "My Op", e.(*structured).data["error_op"], "Op should be available from cause") + + for _, call := range e3.(*structured).callStack { + t.Logf("at %v", call) + } + } + + e3 := Wrap(fmt.Errorf("I'm wrapping your text: %v", firstErr)).With("a", 2) + assert.Nil(t, e3.(*structured).cause, "Wrapping an *Error that's no longer buffered should have yielded no cause") +} + +func TestNewWithCause(t *testing.T) { + cause := buildCause() + outer := New("Hello %v", cause) + assert.Equal(t, "Hello World", hidden.Clean(outer.Error())) + assert.Equal(t, "Hello %v", outer.(*structured).ErrorClean()) + assert.Equal(t, + "github.com/getlantern/errors.TestNewWithCause (errors_test.go:999)", + replaceNumbers.ReplaceAllString(outer.(*structured).data["error_location"].(string), "999")) + assert.Equal(t, cause, outer.(*structured).cause) + + // Make sure that stacktrace prints out okay + buf := &bytes.Buffer{} + print := outer.MultiLinePrinter() + for { + more := print(buf) + buf.WriteByte('\n') + if !more { + break + } + } + expected := `Hello World + at github.com/getlantern/errors.TestNewWithCause (errors_test.go:999) + at testing.tRunner (testing.go:999) + at runtime.goexit (asm_amd999.s:999) +Caused by: World + at github.com/getlantern/errors.buildCause (errors_test.go:999) + at github.com/getlantern/errors.TestNewWithCause (errors_test.go:999) + at testing.tRunner (testing.go:999) + at runtime.goexit (asm_amd999.s:999) +Caused by: orld +Caused by: ld + at github.com/getlantern/errors.buildSubSubCause (errors_test.go:999) + at github.com/getlantern/errors.buildSubCause (errors_test.go:999) + at github.com/getlantern/errors.buildCause (errors_test.go:999) + at github.com/getlantern/errors.TestNewWithCause (errors_test.go:999) + at testing.tRunner (testing.go:999) + at runtime.goexit (asm_amd999.s:999) +Caused by: d +` + + assert.Equal(t, expected, replaceNumbers.ReplaceAllString(hidden.Clean(buf.String()), "999")) + assert.Equal(t, buildSubSubSubCause(), outer.RootCause()) +} + +func buildCause() Error { + return New("W%v", buildSubCause()) +} + +func buildSubCause() error { + return fmt.Errorf("or%v", buildSubSubCause()) +} + +func buildSubSubCause() error { + return New("l%v", buildSubSubSubCause()) +} + +func buildSubSubSubCause() error { + return fmt.Errorf("d") +} + +func TestWrapNil(t *testing.T) { + assert.Nil(t, doWrapNil()) +} + +func doWrapNil() error { + return Wrap(nil) +} + +func TestHiddenWithCause(t *testing.T) { + e1 := fmt.Errorf("I failed %v", "dude") + e2 := New("I wrap: %v", e1) + e3 := fmt.Errorf("Hiding %v", e2) + // clear hidden buffer + hiddenErrors = make([]*structured, 100) + e4 := Wrap(e3) + e5 := New("I'm really outer: %v", e4) + + buf := &bytes.Buffer{} + print := e5.MultiLinePrinter() + for { + more := print(buf) + buf.WriteByte('\n') + if !more { + break + } + } + fmt.Println(buf.String()) + // We're not asserting the output because we're just making sure that printing + // doesn't panic. If we get to this point without panicking, we're happy. +} diff --git a/vendor/github.com/getlantern/errors/hide.go b/vendor/github.com/getlantern/errors/hide.go new file mode 100644 index 0000000..f10d863 --- /dev/null +++ b/vendor/github.com/getlantern/errors/hide.go @@ -0,0 +1,50 @@ +package errors + +import ( + "encoding/binary" + "sync" + + "github.com/getlantern/hidden" +) + +var ( + hiddenErrors = make([]*structured, 100) + nextID = uint64(0) + hiddenMutex sync.RWMutex +) + +// This trick saves the error to a ring buffer and embeds a non-printing +// hiddenID in the error's description, so that if the errors is later wrapped +// by a standard error using something like +// fmt.Errorf("An error occurred: %v", thisError), we can subsequently extract +// the error simply using the hiddenID in the string. +func (e *structured) save() { + hiddenMutex.Lock() + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, nextID) + e.id = nextID + e.hiddenID = hidden.ToString(b) + hiddenErrors[idxForID(nextID)] = e + nextID++ + hiddenMutex.Unlock() +} + +func get(hiddenID []byte) Error { + if len(hiddenID) != 8 { + return nil + } + id := binary.BigEndian.Uint64(hiddenID) + hiddenMutex.RLock() + err := hiddenErrors[idxForID(id)] + hiddenMutex.RUnlock() + if err != nil && err.id == id { + // Found it! + return err + } + // buffer has rolled over + return nil +} + +func idxForID(id uint64) int { + return int(id % uint64(len(hiddenErrors))) +} |