Skip to content

Commit 5916999

Browse files
author
Dean Karn
committed
4.0.0 Updates
- rewrote stack frame logic from scratch. - updated how built in helpers can be used with just an import. - updated helpers not allowing multiple of the same handler to be registered.
1 parent 9aa88f6 commit 5916999

File tree

15 files changed

+142
-199
lines changed

15 files changed

+142
-199
lines changed

Makefile

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
GOCMD=go
22

33
linters-install:
4-
@gometalinter --version >/dev/null 2>&1 || { \
4+
@golangci-lint --version >/dev/null 2>&1 || { \
55
echo "installing linting tools..."; \
6-
$(GOCMD) get github.com/alecthomas/gometalinter; \
7-
gometalinter --install; \
6+
$(GOCMD) get github.com/golangci/golangci-lint/cmd/golangci-lint; \
87
}
98

109
lint: linters-install
11-
gometalinter --vendor --disable-all --enable=vet --enable=vetshadow --enable=golint --enable=megacheck --enable=ineffassign --enable=misspell --enable=errcheck --enable=goconst ./...
10+
golangci-lint run
1211

1312
test:
1413
$(GOCMD) test -cover -race ./...

README.md

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package errors
22
============
3-
![Project status](https://img.shields.io/badge/version-3.3.0-green.svg)
3+
![Project status](https://img.shields.io/badge/version-4.0.0-green.svg)
44
[![Build Status](https://semaphoreci.com/api/v1/joeybloggs/errors/branches/master/badge.svg)](https://semaphoreci.com/joeybloggs/errors)
55
[![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/errors)](https://goreportcard.com/report/github.com/go-playground/errors)
66
[![GoDoc](https://godoc.org/github.com/go-playground/errors?status.svg)](https://godoc.org/github.com/go-playground/errors)
@@ -13,12 +13,18 @@ stack traces, tags(additional information) and even a Type classification system
1313
Common Questions
1414

1515
Why another package?
16-
Because IMO most of the existing packages either don't take the error handling far enough, too far or down right unfriendly to use/consume.
16+
There are two main reasons.
17+
- I think that the programs generating the original error(s) should be responsible for handling them, even though this package allows access to the original error, and that the callers are mainly interested in:
18+
- If the error is Transient or Permanent for retries.
19+
- Additional details for logging.
20+
21+
- IMO most of the existing packages either don't take the error handling far enough, too far or down right unfriendly to use/consume.
1722

1823
Features
1924
--------
2025
- [x] works with go-playground/log, the Tags will be added as Field Key Values and Types will be concatenated as well when using `WithError`
2126
- [x] helpers to extract and classify error types using `RegisterHelper(...)`, many already existing such as ioerrors, neterrors, awserrors...
27+
- [x] built in helpers only need to be imported, eg. `_ github.com/go-playground/errors/helpers/neterrors` allowing libraries to register their own helpers not needing the caller to do or guess what needs to be imported.
2228

2329
Installation
2430
------------
@@ -44,15 +50,24 @@ func main() {
4450
fmt.Println(err)
4551
if errors.HasType(err, "Permanent") {
4652
// os.Exit(1)
53+
fmt.Println("it is a permanent error")
4754
}
4855

4956
// root error
5057
cause := errors.Cause(err)
51-
fmt.Println(cause)
58+
fmt.Println("CAUSE:", cause)
5259

5360
// can even still inspect the internal error
5461
fmt.Println(errors.Cause(err) == io.EOF) // will extract the cause for you
5562
fmt.Println(errors.Cause(cause) == io.EOF)
63+
64+
// and still in a switch
65+
switch errors.Cause(err) {
66+
case io.EOF:
67+
fmt.Println("EOF error")
68+
default:
69+
fmt.Println("unknown error")
70+
}
5671
}
5772

5873
func level1(value string) error {
@@ -63,7 +78,7 @@ func level1(value string) error {
6378
}
6479

6580
func level2(value string) error {
66-
err := fmt.Errorf("this is an %s", "error")
81+
err := io.EOF
6782
return errors.Wrap(err, "failed to do something").AddTypes("Permanent").AddTags(errors.T("value", value))
6883
}
6984
```
@@ -76,35 +91,23 @@ package main
7691
import (
7792
"fmt"
7893

79-
"strings"
80-
8194
"github.com/go-playground/errors"
8295
)
8396

8497
func main() {
8598
// maybe you just want to grab a stack trace and process on your own like go-playground/log
8699
// uses it to produce a stack trace log message
87100
frame := errors.Stack()
88-
name := fmt.Sprintf("%n", frame)
89-
file := fmt.Sprintf("%+s", frame)
90-
line := fmt.Sprintf("%d", frame)
91-
parts := strings.Split(file, "\n\t")
92-
if len(parts) > 1 {
93-
file = parts[1]
94-
}
101+
fmt.Printf("Function: %s File: %s Line: %d\n", frame.Function(), frame.File(), frame.Line())
95102

96-
fmt.Printf("Name: %s File: %s Line: %s\n", name, file, line)
103+
// and still have access to the underlying runtime.Frame
104+
fmt.Printf("%+v\n", frame.Frame)
97105
}
98106
```
99107

100108
Package Versioning
101109
----------
102-
I'm jumping on the vendoring bandwagon, you should vendor this package as I will not
103-
be creating different version with gopkg.in like allot of my other libraries.
104-
105-
Why? because my time is spread pretty thin maintaining all of the libraries I have + LIFE,
106-
it is so freeing not to worry about it and will help me keep pouring out bigger and better
107-
things for you the community.
110+
Using Go modules and proper semantic version releases (as always).
108111

109112
License
110113
------

_examples/basic/main.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,24 @@ func main() {
1212
fmt.Println(err)
1313
if errors.HasType(err, "Permanent") {
1414
// os.Exit(1)
15+
fmt.Println("it is a permanent error")
1516
}
1617

1718
// root error
1819
cause := errors.Cause(err)
19-
fmt.Println(cause)
20+
fmt.Println("CAUSE:", cause)
2021

2122
// can even still inspect the internal error
2223
fmt.Println(errors.Cause(err) == io.EOF) // will extract the cause for you
2324
fmt.Println(errors.Cause(cause) == io.EOF)
25+
26+
// and still in a switch
27+
switch errors.Cause(err) {
28+
case io.EOF:
29+
fmt.Println("EOF error")
30+
default:
31+
fmt.Println("unknown error")
32+
}
2433
}
2534

2635
func level1(value string) error {
@@ -31,6 +40,6 @@ func level1(value string) error {
3140
}
3241

3342
func level2(value string) error {
34-
err := fmt.Errorf("this is an %s", "error")
43+
err := io.EOF
3544
return errors.Wrap(err, "failed to do something").AddTypes("Permanent").AddTags(errors.T("value", value))
3645
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import (
55
"net"
66

77
"github.com/go-playground/errors"
8-
"github.com/go-playground/errors/helpers/neterrors"
8+
// init function handles registration automatically
9+
_ "github.com/go-playground/errors/helpers/neterrors"
910
)
1011

1112
func main() {
12-
errors.RegisterHelper(neterrors.NETErrors)
1313
_, err := net.ResolveIPAddr("tcp", "foo")
1414
if err != nil {
1515
err = errors.Wrap(err, "failed to perform operation")

_examples/helpers/custom/main.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"net"
6+
7+
"github.com/go-playground/errors"
8+
)
9+
10+
func main() {
11+
errors.RegisterHelper(MyCustomErrHandler)
12+
_, err := net.ResolveIPAddr("tcp", "foo")
13+
if err != nil {
14+
err = errors.Wrap(err, "failed to perform operation")
15+
}
16+
17+
// all that extra context, types and tags captured for free
18+
// there are more helpers and you can even create your own.
19+
fmt.Println(err)
20+
}
21+
22+
// MyCustomErrHandler helps classify my errors
23+
func MyCustomErrHandler(c errors.Chain, err error) (cont bool) {
24+
switch err.(type) {
25+
case net.UnknownNetworkError:
26+
_ = c.AddTypes("io").AddTag("additional", "details")
27+
return
28+
//case net.Other:
29+
// ...
30+
// return
31+
}
32+
return true
33+
}

_examples/stack/main.go

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,15 @@ package main
33
import (
44
"fmt"
55

6-
"strings"
7-
86
"github.com/go-playground/errors"
97
)
108

119
func main() {
1210
// maybe you just want to grab a stack trace and process on your own like go-playground/log
1311
// uses it to produce a stack trace log message
1412
frame := errors.Stack()
15-
name := fmt.Sprintf("%n", frame)
16-
file := fmt.Sprintf("%+s", frame)
17-
line := fmt.Sprintf("%d", frame)
18-
parts := strings.Split(file, "\n\t")
19-
if len(parts) > 1 {
20-
file = parts[1]
21-
}
13+
fmt.Printf("Function: %s File: %s Line: %d\n", frame.Function(), frame.File(), frame.Line())
2214

23-
fmt.Printf("Name: %s File: %s Line: %s\n", name, file, line)
15+
// and still have access to the underlying runtime.Frame
16+
fmt.Printf("%+v\n", frame.Frame)
2417
}

chain.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ func T(key string, value interface{}) Tag {
1010
return Tag{Key: key, Value: value}
1111
}
1212

13-
// Tag contains a single key value conbination
13+
// Tag contains a single key value combination
1414
// to be attached to your error
1515
type Tag struct {
1616
Key string
@@ -21,7 +21,7 @@ func newLink(err error, prefix string, skipFrames int) *Link {
2121
return &Link{
2222
Err: err,
2323
Prefix: prefix,
24-
Source: st(skipFrames),
24+
Source: StackLevel(skipFrames),
2525
}
2626

2727
}
@@ -57,12 +57,12 @@ type Link struct {
5757
Tags []Tag
5858

5959
// Source contains the name, file and lines obtained from the stack trace
60-
Source string
60+
Source Frame
6161
}
6262

6363
// formatError prints a single Links error
6464
func (l *Link) formatError() string {
65-
line := fmt.Sprintf("source=%s ", l.Source)
65+
line := fmt.Sprintf("source=%s: %s:%d ", l.Source.Function(), l.Source.File(), l.Source.Line())
6666

6767
if l.Prefix != "" {
6868
line += l.Prefix
@@ -114,5 +114,5 @@ func (c Chain) AddTypes(typ ...string) Chain {
114114

115115
// Wrap adds another contextual prefix to the error chain
116116
func (c Chain) Wrap(prefix string) Chain {
117-
return wrap(c, prefix, 0)
117+
return wrap(c, prefix, 3)
118118
}

errors.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package errors
33
import (
44
"errors"
55
"fmt"
6+
"reflect"
67
)
78

89
var (
@@ -13,37 +14,42 @@ var (
1314
// errors will run all registered helpers until a match is found.
1415
// NOTE helpers are run in the order they are added.
1516
func RegisterHelper(helper Helper) {
17+
for i := 0; i < len(helpers); i++ {
18+
if reflect.ValueOf(helpers[i]).Pointer() == reflect.ValueOf(helper).Pointer() {
19+
return
20+
}
21+
}
1622
helpers = append(helpers, helper)
1723
}
1824

1925
// New creates an error with the provided text and automatically wraps it with line information.
2026
func New(s string) Chain {
21-
return wrap(errors.New(s), "", 0)
27+
return wrap(errors.New(s), "", 3)
2228
}
2329

2430
// Newf creates an error with the provided text and automatically wraps it with line information.
2531
// it also accepts a varadic for optional message formatting.
2632
func Newf(format string, a ...interface{}) Chain {
27-
return wrap(fmt.Errorf(format, a...), "", 0)
33+
return wrap(fmt.Errorf(format, a...), "", 3)
2834
}
2935

3036
// Wrap encapsulates the error, stores a contextual prefix and automatically obtains
3137
// a stack trace.
3238
func Wrap(err error, prefix string) Chain {
33-
return wrap(err, prefix, 0)
39+
return wrap(err, prefix, 3)
3440
}
3541

3642
// Wrapf encapsulates the error, stores a contextual prefix and automatically obtains
3743
// a stack trace.
3844
// it also accepts a varadic for prefix formatting.
3945
func Wrapf(err error, prefix string, a ...interface{}) Chain {
40-
return wrap(err, fmt.Sprintf(prefix, a...), 0)
46+
return wrap(err, fmt.Sprintf(prefix, a...), 3)
4147
}
4248

4349
// WrapSkipFrames is a special version of Wrap that skips extra n frames when determining error location.
4450
// Normally only used when wrapping the library
4551
func WrapSkipFrames(err error, prefix string, n uint) Chain {
46-
return wrap(err, prefix, int(n))
52+
return wrap(err, prefix, int(n)+3)
4753
}
4854

4955
func wrap(err error, prefix string, skipFrames int) (c Chain) {

errors_test.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,9 @@ func TestWrap(t *testing.T) {
5959

6060
for i, tt := range tests {
6161
link := tt.err.current()
62-
if !strings.HasSuffix(link.Source, tt.suf) || !strings.HasPrefix(link.Source, tt.pre) {
63-
t.Fatalf("IDX: %d want %s<path>%s got %s", i, tt.pre, tt.suf, link.Source)
62+
source := fmt.Sprintf("%s: %s:%d", link.Source.Function(), link.Source.File(), link.Source.Line())
63+
if !strings.HasSuffix(source, tt.suf) || !strings.HasPrefix(source, tt.pre) {
64+
t.Fatalf("IDX: %d want %s<path>%s got %s", i, tt.pre, tt.suf, source)
6465
}
6566
}
6667
}
@@ -158,8 +159,8 @@ func TestCause2(t *testing.T) {
158159
}
159160

160161
func TestHelpers(t *testing.T) {
161-
fn := func(w Chain, err error) (cont bool) {
162-
w.AddTypes("Test").AddTags(T("test", "tag")).AddTag("foo", "bar")
162+
fn := func(w Chain, _ error) (cont bool) {
163+
_ = w.AddTypes("Test").AddTags(T("test", "tag")).AddTag("foo", "bar")
163164
return false
164165
}
165166
RegisterHelper(fn)

helpers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package errors
22

33
// Helper is a function which will automatically extract Type and Tag information based on the supplied err and
4-
// add it to the supplied *Link error; this can be used independently or by registering using errors.REgisterHelper(...),
4+
// add it to the supplied *Link error; this can be used independently or by registering using errors.RegisterHelper(...),
55
// which will run the registered helper every time errors.Wrap(...) is called.
66
type Helper func(Chain, error) bool

0 commit comments

Comments
 (0)