Skip to content

Commit a9c0873

Browse files
committed
Refactor the package
- Add support for Go modules - Add support for Go >= 1.13 errors - Improve the stack trace serialization - Rename the "context" for "data" to avoid conflicts with Go context.Context - Add a new example
1 parent 3a3f715 commit a9c0873

File tree

11 files changed

+438
-208
lines changed

11 files changed

+438
-208
lines changed

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
tests:
2+
go test -v ./...

README.md

Lines changed: 170 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,28 @@
11
# errors [![GoDoc](https://godoc.org/github.com/zignd/errors?status.svg)](https://godoc.org/github.com/zignd/errors) [![Report card](https://goreportcard.com/badge/github.com/zignd/errors)](https://goreportcard.com/report/github.com/zignd/errors)
22

3-
An errors package that will help you handle them gracefully. It allows you to add contextual information to your errors, to wrap them and they even get a stack trace. Inspired by the [github.com/pkg/errors](https://www.github.com/pkg/errors) package and Node.js' [verror](https://github.com/joyent/node-verror) module.
3+
An errors package that will help you handle them gracefully. It allows you to add additional data to your errors, to wrap it and you even get a stack trace. Inspired by the [github.com/pkg/errors](https://www.github.com/pkg/errors) package and Node.js' [verror](https://github.com/joyent/node-verror) module.
44

55
# Features
66

7-
* Add contextual information to error values preventing long and hard to read error messages
7+
* Add additional data to error values preventing long and hard to read error messages
88
* Wrap existing error values into new ones
9-
* Stack traces
9+
* Stack traces for each error value
1010
* MultiError, wrap multiple errors values into a single one; great for concurrent workflows that may generate multiple errors
1111
* Pretty print of the whole error value and support JSON marshalling to ease the serialization (check the ["Quick demo"](https://github.com/zignd/errors#quick-demo) section)
1212

13+
# Installation
14+
15+
```bash
16+
go get -u github.com/zignd/errors
17+
```
18+
1319
# Documentation
1420

1521
For a better understanding of the features provided by the package check the documentation at: [godoc.org/github.com/zignd/errors](https://godoc.org/github.com/zignd/errors)
1622

1723
# Quick demo
1824

19-
**Consider the following usages of the package in the functions defined below**
25+
There's an example at `examples/example1/example1.go` that shows how to use the package. Here's the code for the example:
2026

2127
```go
2228
package main
@@ -28,126 +34,184 @@ import (
2834
"github.com/zignd/errors"
2935
)
3036

31-
func foo() error {
32-
model := "iop-40392"
33-
34-
if err := launch(model); err != nil {
35-
return errors.Wrapc(err, map[string]interface{}{
36-
"model": model,
37-
}, "failed to launch rocket")
37+
func createTransaction(id string) error {
38+
bank := "bank_123456"
39+
if err := updateDatabase(); err != nil {
40+
return errors.Wrapdf(err, errors.Data{
41+
"transactionId": id,
42+
"userId": "67890",
43+
}, "failed to complete the transaction on %s", bank)
3844
}
3945

4046
return nil
4147
}
4248

43-
func launch(model string) error {
44-
return errors.Errorc(map[string]interface{}{
45-
"rocket": map[string]interface{}{
46-
"ID": "123",
47-
"Fuel": 10,
48-
"AutoPilot": true,
49-
},
50-
}, "something catastrofic just happened to rocket #123")
51-
}
52-
```
49+
func updateDatabase() error {
50+
if err := createConnection(); err != nil {
51+
return errors.Wrapd(err, errors.Data{
52+
"tableName": "transactions",
53+
"operation": "update",
54+
}, "failed to update the database")
55+
}
5356

54-
**JSON marshalling an error value**
57+
return nil
58+
}
5559

56-
```go
57-
func main() {
58-
if err := foo(); err != nil {
59-
// Type assertions using the exposed Error type
60-
if err, ok := err.(*errors.Error); ok {
61-
b, _ := json.MarshalIndent(err, "", "\t")
62-
fmt.Printf("%s", b)
63-
}
60+
func createConnection() error {
61+
if err := open(); err != nil {
62+
return errors.Wrapd(err, errors.Data{
63+
"server": "db-server-01",
64+
"timeoutSeconds": 30,
65+
}, "connection timeout")
6466
}
67+
68+
return nil
6569
}
66-
```
6770

68-
**Output**
71+
func open() error {
72+
return errors.Errord(errors.Data{
73+
"network": "internal",
74+
"severity": "high",
75+
}, "network instability detected")
76+
}
6977

70-
```
71-
{
72-
"Message": "failed to launch rocket",
73-
"Context": {
74-
"model": "iop-40392"
75-
},
76-
"Stack": "main.foo\n\t/home/zignd/go/src/github.com/zignd/test/main.go:38\nmain.main\n\t/home/zignd/go/src/github.com/zignd/test/main.go:11\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:194\nruntime.goexit\n\t/usr/local/go/src/runtime/asm_amd64.s:2198",
77-
"Cause": {
78-
"Message": "something catastrofic just happened to rocket #123",
79-
"Context": {
80-
"rocket": {
81-
"AutoPilot": true,
82-
"Fuel": 10,
83-
"ID": "123"
84-
}
85-
},
86-
"Stack": "main.launch\n\t/home/zignd/go/src/github.com/zignd/test/main.go:51\nmain.foo\n\t/home/zignd/go/src/github.com/zignd/test/main.go:35\nmain.main\n\t/home/zignd/go/src/github.com/zignd/test/main.go:11\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:194\nruntime.goexit\n\t/usr/local/go/src/runtime/asm_amd64.s:2198",
87-
"Cause": null
88-
}
89-
}
90-
```
78+
func main() {
79+
if err := createTransaction("tx_123456"); err != nil {
80+
b, _ := json.MarshalIndent(err, "", " ")
81+
fmt.Println("Error logged as a JSON structure using the JSON.MarshalIndent:")
82+
fmt.Printf("%s\n", b)
9183

92-
**`fmt.Formatter` implementation supporting the `+v` format for recursive pretty print of the whole error value**
84+
b, _ = json.Marshal(err)
85+
fmt.Println("\nError logged as a JSON structure using the JSON.Marshal:")
86+
fmt.Printf("%s\n", b)
9387

94-
```go
95-
func main() {
96-
if err := foo(); err != nil {
97-
if err, ok := err.(*errors.Error); ok {
98-
fmt.Printf("%+v", err)
99-
}
88+
fmt.Println("\nError logged using the s format specifier:")
89+
fmt.Printf("%s\n", err)
90+
91+
fmt.Println("\nError logged using the +v format specifier:")
92+
fmt.Printf("%+v\n", err)
10093
}
10194
}
10295
```
10396

104-
**Output**
97+
Here's the execution of the example:
10598

10699
```
107-
Message:
108-
"failed to launch rocket"
109-
Context:
110-
model: iop-40392
111-
Stack:
112-
main.foo
113-
/home/zignd/go/src/github.com/zignd/test/main.go:38
114-
main.main
115-
/home/zignd/go/src/github.com/zignd/test/main.go:11
116-
runtime.main
117-
/usr/local/go/src/runtime/proc.go:194
118-
runtime.goexit
119-
/usr/local/go/src/runtime/asm_amd64.s:2198
120-
Cause:
121-
Message:
122-
"something catastrofic just happened to rocket #123"
123-
Context:
124-
rocket: map[ID:123 Fuel:10 AutoPilot:true]
125-
Stack:
126-
main.launch
127-
/home/zignd/go/src/github.com/zignd/test/main.go:51
128-
main.foo
129-
/home/zignd/go/src/github.com/zignd/test/main.go:35
130-
main.main
131-
/home/zignd/go/src/github.com/zignd/test/main.go:11
132-
runtime.main
133-
/usr/local/go/src/runtime/proc.go:194
134-
runtime.goexit
135-
/usr/local/go/src/runtime/asm_amd64.s:2198
136-
```
137-
138-
**The usual `%s` format support**
139-
140-
```go
141-
func main() {
142-
if err := foo(); err != nil {
143-
if err, ok := err.(*errors.Error); ok {
144-
fmt.Printf("%s", err)
145-
}
146-
}
100+
$ go run examples/example1/example1.go
101+
Error logged as a JSON structure using the JSON.MarshalIndent:
102+
{
103+
"message": "failed to complete the transaction on bank_123456",
104+
"data": {
105+
"transactionId": "tx_123456",
106+
"userId": "67890"
107+
},
108+
"stack": [
109+
"main.createTransaction @ /root/hack/errors/examples/example1/example1.go:13",
110+
"main.main @ /root/hack/errors/examples/example1/example1.go:52",
111+
"runtime/internal/atomic.(*Uint32).Load @ /root/go/version/go1.21.0/src/runtime/internal/atomic/types.go:194",
112+
"runtime.goexit @ /root/go/version/go1.21.0/src/runtime/asm_amd64.s:1651"
113+
],
114+
"cause": {
115+
"message": "failed to update the database",
116+
"data": {
117+
"operation": "update",
118+
"tableName": "transactions"
119+
},
120+
"stack": [
121+
"main.updateDatabase @ /root/hack/errors/examples/example1/example1.go:24",
122+
"main.createTransaction @ /root/hack/errors/examples/example1/example1.go:12",
123+
"main.main @ /root/hack/errors/examples/example1/example1.go:52",
124+
"runtime/internal/atomic.(*Uint32).Load @ /root/go/version/go1.21.0/src/runtime/internal/atomic/types.go:194",
125+
"runtime.goexit @ /root/go/version/go1.21.0/src/runtime/asm_amd64.s:1651"
126+
],
127+
"cause": {
128+
"message": "connection timeout",
129+
"data": {
130+
"server": "db-server-01",
131+
"timeoutSeconds": 30
132+
},
133+
"stack": [
134+
"main.createConnection @ /root/hack/errors/examples/example1/example1.go:35",
135+
"main.updateDatabase @ /root/hack/errors/examples/example1/example1.go:23",
136+
"main.createTransaction @ /root/hack/errors/examples/example1/example1.go:12",
137+
"main.main @ /root/hack/errors/examples/example1/example1.go:52",
138+
"runtime/internal/atomic.(*Uint32).Load @ /root/go/version/go1.21.0/src/runtime/internal/atomic/types.go:194",
139+
"runtime.goexit @ /root/go/version/go1.21.0/src/runtime/asm_amd64.s:1651"
140+
],
141+
"cause": {
142+
"message": "network instability detected",
143+
"data": {
144+
"network": "internal",
145+
"severity": "high"
146+
},
147+
"stack": [
148+
"main.open @ /root/hack/errors/examples/example1/example1.go:45",
149+
"main.createConnection @ /root/hack/errors/examples/example1/example1.go:34",
150+
"main.updateDatabase @ /root/hack/errors/examples/example1/example1.go:23",
151+
"main.createTransaction @ /root/hack/errors/examples/example1/example1.go:12",
152+
"main.main @ /root/hack/errors/examples/example1/example1.go:52",
153+
"runtime/internal/atomic.(*Uint32).Load @ /root/go/version/go1.21.0/src/runtime/internal/atomic/types.go:194",
154+
"runtime.goexit @ /root/go/version/go1.21.0/src/runtime/asm_amd64.s:1651"
155+
]
156+
}
157+
}
158+
}
147159
}
148-
```
149-
**Output**
150160
151-
```
152-
failed to launch rocket: something catastrofic just happened to rocket #123
153-
```
161+
Error logged as a JSON structure using the JSON.Marshal:
162+
{"message":"failed to complete the transaction on bank_123456","data":{"transactionId":"tx_123456","userId":"67890"},"stack":["main.createTransaction @ /root/hack/errors/examples/example1/example1.go:13","main.main @ /root/hack/errors/examples/example1/example1.go:52","runtime/internal/atomic.(*Uint32).Load @ /root/go/version/go1.21.0/src/runtime/internal/atomic/types.go:194","runtime.goexit @ /root/go/version/go1.21.0/src/runtime/asm_amd64.s:1651"],"cause":{"message":"failed to update the database","data":{"operation":"update","tableName":"transactions"},"stack":["main.updateDatabase @ /root/hack/errors/examples/example1/example1.go:24","main.createTransaction @ /root/hack/errors/examples/example1/example1.go:12","main.main @ /root/hack/errors/examples/example1/example1.go:52","runtime/internal/atomic.(*Uint32).Load @ /root/go/version/go1.21.0/src/runtime/internal/atomic/types.go:194","runtime.goexit @ /root/go/version/go1.21.0/src/runtime/asm_amd64.s:1651"],"cause":{"message":"connection timeout","data":{"server":"db-server-01","timeoutSeconds":30},"stack":["main.createConnection @ /root/hack/errors/examples/example1/example1.go:35","main.updateDatabase @ /root/hack/errors/examples/example1/example1.go:23","main.createTransaction @ /root/hack/errors/examples/example1/example1.go:12","main.main @ /root/hack/errors/examples/example1/example1.go:52","runtime/internal/atomic.(*Uint32).Load @ /root/go/version/go1.21.0/src/runtime/internal/atomic/types.go:194","runtime.goexit @ /root/go/version/go1.21.0/src/runtime/asm_amd64.s:1651"],"cause":{"message":"network instability detected","data":{"network":"internal","severity":"high"},"stack":["main.open @ /root/hack/errors/examples/example1/example1.go:45","main.createConnection @ /root/hack/errors/examples/example1/example1.go:34","main.updateDatabase @ /root/hack/errors/examples/example1/example1.go:23","main.createTransaction @ /root/hack/errors/examples/example1/example1.go:12","main.main @ /root/hack/errors/examples/example1/example1.go:52","runtime/internal/atomic.(*Uint32).Load @ /root/go/version/go1.21.0/src/runtime/internal/atomic/types.go:194","runtime.goexit @ /root/go/version/go1.21.0/src/runtime/asm_amd64.s:1651"]}}}}
163+
164+
Error logged using the s format specifier:
165+
failed to complete the transaction on bank_123456: failed to update the database: connection timeout: network instability detected
166+
167+
Error logged using the +v format specifier:
168+
message:
169+
"failed to complete the transaction on bank_123456"
170+
data:
171+
transactionId: tx_123456
172+
userId: 67890
173+
stack:
174+
main.createTransaction @ /root/hack/errors/examples/example1/example1.go:13
175+
main.main @ /root/hack/errors/examples/example1/example1.go:52
176+
runtime/internal/atomic.(*Uint32).Load @ /root/go/version/go1.21.0/src/runtime/internal/atomic/types.go:194
177+
runtime.goexit @ /root/go/version/go1.21.0/src/runtime/asm_amd64.s:1651
178+
cause:
179+
message:
180+
"failed to update the database"
181+
data:
182+
tableName: transactions
183+
operation: update
184+
stack:
185+
main.updateDatabase @ /root/hack/errors/examples/example1/example1.go:24
186+
main.createTransaction @ /root/hack/errors/examples/example1/example1.go:12
187+
main.main @ /root/hack/errors/examples/example1/example1.go:52
188+
runtime/internal/atomic.(*Uint32).Load @ /root/go/version/go1.21.0/src/runtime/internal/atomic/types.go:194
189+
runtime.goexit @ /root/go/version/go1.21.0/src/runtime/asm_amd64.s:1651
190+
cause:
191+
message:
192+
"connection timeout"
193+
data:
194+
server: db-server-01
195+
timeoutSeconds: 30
196+
stack:
197+
main.createConnection @ /root/hack/errors/examples/example1/example1.go:35
198+
main.updateDatabase @ /root/hack/errors/examples/example1/example1.go:23
199+
main.createTransaction @ /root/hack/errors/examples/example1/example1.go:12
200+
main.main @ /root/hack/errors/examples/example1/example1.go:52
201+
runtime/internal/atomic.(*Uint32).Load @ /root/go/version/go1.21.0/src/runtime/internal/atomic/types.go:194
202+
runtime.goexit @ /root/go/version/go1.21.0/src/runtime/asm_amd64.s:1651
203+
cause:
204+
message:
205+
"network instability detected"
206+
data:
207+
network: internal
208+
severity: high
209+
stack:
210+
main.open @ /root/hack/errors/examples/example1/example1.go:45
211+
main.createConnection @ /root/hack/errors/examples/example1/example1.go:34
212+
main.updateDatabase @ /root/hack/errors/examples/example1/example1.go:23
213+
main.createTransaction @ /root/hack/errors/examples/example1/example1.go:12
214+
main.main @ /root/hack/errors/examples/example1/example1.go:52
215+
runtime/internal/atomic.(*Uint32).Load @ /root/go/version/go1.21.0/src/runtime/internal/atomic/types.go:194
216+
runtime.goexit @ /root/go/version/go1.21.0/src/runtime/asm_amd64.s:1651
217+
```

error.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package errors
2+
3+
import "fmt"
4+
5+
// Error is the error struct used internally by the package. This type should only be used for type assertions.
6+
type Error struct {
7+
Message string `json:"message"`
8+
Data Data `json:"data,omitempty"`
9+
Stack Stack `json:"stack"`
10+
Cause error `json:"cause,omitempty"`
11+
}
12+
13+
func (e Error) Error() string {
14+
if e.Cause != nil {
15+
return fmt.Sprintf("%s: %s", e.Message, e.Cause.Error())
16+
}
17+
18+
return e.Message
19+
}
20+
21+
// Format implements fmt.Formatter. It only accepts the '+v' and 's' formats.
22+
func (e Error) Format(s fmt.State, verb rune) {
23+
if verb == 'v' && s.Flag('+') {
24+
fmt.Fprintf(s, "%s", format(e, 0))
25+
} else {
26+
fmt.Fprintf(s, "%s", e.Error())
27+
}
28+
}
29+
30+
func (e Error) Unwrap() error {
31+
return e.Cause
32+
}

0 commit comments

Comments
 (0)