Skip to content

Commit 37cb891

Browse files
committed
move from pkg/errors to std lib and support go 1.13 errors
1 parent 481d5fc commit 37cb891

File tree

9 files changed

+994
-170
lines changed

9 files changed

+994
-170
lines changed

README.md

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,35 @@
22

33
# errors
44

5-
My drop-in replacement for `github.com/pkg/errors`.
5+
A drop-in replacement for creating errors with OpenCensus instrumentation (OpenTelemetry soon).
6+
7+
This package also supports the Go 1.13 `errors.As` and `errors.Is` methods. Check the examples in Go doc.
68

79
I created this package to help me link the details of my Stackdriver traces with Stackdriver logs.
810

911
* https://cloud.google.com/trace/docs/viewing-details
1012
* https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
1113
* https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogEntrySourceLocation
1214

13-
## Goals
14-
15-
Add OpenCensus trace attributes when creating a new error.
16-
17-
## Basic Usage
15+
## Getting Started
1816

1917
Drop-in replacement. Creating an error.
2018

2119
```golang
22-
err := errors.New("msg") // Wrap .. Wrapf .. Errof ..
20+
err := errors.New("msg") // Wrap .. Wrapf .. Errorf ..
2321
```
2422

2523
Creating an error with context useful for monitoring.
2624

2725
```golang
2826
func main() {
29-
_, span := trace.StartSpan(context.Background(), "ExampleNewT")
27+
_, span := trace.StartSpan(context.Background(), "main")
3028
defer span.End()
3129

3230
err := errors.NewT(span, "error")
3331
fmt.Println(err)
3432

35-
if erctx, ok := err.(errors.Error); ok {
33+
if erctx, ok := err.(errors.ErrorTracer); ok {
3634
fmt.Println(erctx.SourceLocation().Function)
3735
fmt.Println(erctx.SourceLocation().File)
3836
fmt.Println(erctx.SourceLocation().Line)

errors.go

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
package errors
2+
3+
import (
4+
"fmt"
5+
"runtime"
6+
7+
"errors"
8+
9+
"go.opencensus.io/trace"
10+
)
11+
12+
// Overwrite these values during build via -ldflags.
13+
var (
14+
// VERSION is the app-global version.
15+
VERSION = "UNKNOWN"
16+
17+
// COMMIT is the app-global commit id.
18+
COMMIT = "UNKNOWN"
19+
20+
// BRANCH is the app-global git branch.
21+
BRANCH = "UNKNOWN"
22+
)
23+
24+
const (
25+
wrappedFunctionCallDepth = 2
26+
)
27+
28+
// Tracer represents an error that has TraceContext and SourceLocation.
29+
type Tracer interface {
30+
SourceLocation() SourceLocation
31+
TraceContext() TraceContext
32+
SetTraceContext(trace.SpanContext)
33+
SetSourceLocation(depth int)
34+
}
35+
36+
// TraceContext is used to provide a tracing context to an object for logging purposes.
37+
// This is helpful for Developers to link Stackdriver traces to Stackdriver logs.
38+
// See https://cloud.google.com/trace/docs/viewing-details.
39+
// See https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry.
40+
type TraceContext struct {
41+
TraceID string `json:"trace"`
42+
SpanID string `json:"spanId"`
43+
}
44+
45+
// SourceLocation provides the information where the actual error happened in the code.
46+
// https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogEntrySourceLocation
47+
type SourceLocation struct {
48+
Function string `json:"function"`
49+
File string `json:"file"`
50+
Line int `json:"line"`
51+
Version string `json:"version,omitempty"`
52+
Commit string `json:"commit,omitempty"`
53+
Branch string `json:"branch,omitempty"`
54+
}
55+
56+
// NewSourceLocation creates a SourceLocation using stdlib runtime.Caller.
57+
func NewSourceLocation(depth int) SourceLocation {
58+
function, file, line, _ := runtime.Caller(depth)
59+
return SourceLocation{
60+
runtime.FuncForPC(function).Name(), file, line, VERSION, COMMIT, BRANCH,
61+
}
62+
}
63+
64+
// As is a drop-in replacement for errors.As method.
65+
func As(target error, dest interface{}) bool {
66+
return errors.As(target, dest)
67+
}
68+
69+
// Is is a drop-in replacement for errors.Is method.
70+
func Is(err, target error) bool {
71+
return errors.Is(err, target)
72+
}
73+
74+
// ErrorTracer represents an error with tracing.
75+
type ErrorTracer interface {
76+
error
77+
// Unwrap is used to implement the Go 1.13 error wrapping technique.
78+
// This makes the implementation of ErrorTracer to make use of errors.Is and errors.As.
79+
Unwrap() error
80+
Tracer
81+
}
82+
83+
// Compile time implementation check.
84+
var _ ErrorTracer = &errorContext{}
85+
86+
type errorContext struct {
87+
err error
88+
sourceLocation SourceLocation
89+
traceContext TraceContext
90+
}
91+
92+
func (e *errorContext) Unwrap() error {
93+
return e.err
94+
}
95+
96+
func (e *errorContext) Error() string {
97+
return e.err.Error()
98+
}
99+
100+
func (e *errorContext) SourceLocation() SourceLocation {
101+
return e.sourceLocation
102+
}
103+
104+
func (e *errorContext) SetSourceLocation(depth int) {
105+
e.sourceLocation = NewSourceLocation(depth)
106+
}
107+
108+
func (e *errorContext) TraceContext() TraceContext {
109+
return e.traceContext
110+
}
111+
112+
func (e *errorContext) SetTraceContext(t trace.SpanContext) {
113+
e.traceContext = TraceContext{
114+
TraceID: t.TraceID.String(),
115+
SpanID: t.SpanID.String(),
116+
}
117+
}
118+
119+
// NewCaller wraps errors.New with a specified caller depth.
120+
func NewCaller(depth int, m string) error {
121+
err := &errorContext{
122+
err: errors.New(m),
123+
sourceLocation: NewSourceLocation(depth),
124+
}
125+
return err
126+
}
127+
128+
// NewCallerT wraps errors.New with a specified caller depth and a span trace context.
129+
func NewCallerT(depth int, span *trace.Span, m string) error {
130+
err := &errorContext{
131+
err: errors.New(m),
132+
sourceLocation: NewSourceLocation(depth),
133+
}
134+
return annotate(err, span)
135+
}
136+
137+
// NewCallerf wraps fmt.Errorf with a specified caller depth.
138+
func NewCallerf(depth int, m string, args ...interface{}) error {
139+
err := &errorContext{
140+
err: fmt.Errorf(m, args...),
141+
sourceLocation: NewSourceLocation(depth),
142+
}
143+
return err
144+
}
145+
146+
// NewCallerfT wraps fmt.Errorf with a specified caller depth and a span trace context.
147+
func NewCallerfT(depth int, span *trace.Span, m string, args ...interface{}) error {
148+
err := &errorContext{
149+
err: fmt.Errorf(m, args...),
150+
sourceLocation: NewSourceLocation(depth),
151+
}
152+
return annotate(err, span)
153+
}
154+
155+
// WrapCaller wraps fmt.Errorf with a specified caller depth.
156+
func WrapCaller(depth int, e error, m string) error {
157+
err := &errorContext{
158+
err: fmt.Errorf("%s: %w", m, e),
159+
sourceLocation: NewSourceLocation(depth),
160+
}
161+
return err
162+
}
163+
164+
// WrapCallerT wraps fmt.Errorf with a specified caller depth with a span trace context.
165+
func WrapCallerT(depth int, span *trace.Span, e error, m string) error {
166+
err := &errorContext{
167+
err: fmt.Errorf("%s: %w", m, e),
168+
sourceLocation: NewSourceLocation(depth),
169+
}
170+
return annotate(err, span)
171+
}
172+
173+
// WrapCallerf wraps fmt.Errorf with a specified caller depth.
174+
func WrapCallerf(depth int, e error, format string, args ...interface{}) error {
175+
m := fmt.Sprintf(format, args...)
176+
err := &errorContext{
177+
err: fmt.Errorf("%s: %w", m, e),
178+
sourceLocation: NewSourceLocation(depth),
179+
}
180+
return err
181+
}
182+
183+
// WrapCallerfT wraps fmt.Errorf with a specified caller depth with a span trace context.
184+
func WrapCallerfT(depth int, span *trace.Span, e error, format string, args ...interface{}) error {
185+
m := fmt.Sprintf(format, args...)
186+
err := &errorContext{
187+
err: fmt.Errorf("%s: %w", m, e),
188+
sourceLocation: NewSourceLocation(depth),
189+
}
190+
return annotate(err, span)
191+
}
192+
193+
// New is the drop-in replacement for errors.New.
194+
func New(m string) error {
195+
err := &errorContext{
196+
err: errors.New(m),
197+
sourceLocation: NewSourceLocation(wrappedFunctionCallDepth),
198+
}
199+
return err
200+
}
201+
202+
// NewT wraps errors.New with a span trace context.
203+
func NewT(span *trace.Span, m string) error {
204+
err := &errorContext{
205+
err: errors.New(m),
206+
sourceLocation: NewSourceLocation(wrappedFunctionCallDepth),
207+
}
208+
return annotate(err, span)
209+
}
210+
211+
// Errorf wraps fmt.Errorf.
212+
func Errorf(m string, args ...interface{}) error {
213+
err := &errorContext{
214+
err: fmt.Errorf(m, args...),
215+
sourceLocation: NewSourceLocation(wrappedFunctionCallDepth),
216+
}
217+
return err
218+
}
219+
220+
// ErrorfT wraps fmt.Errorf with a span trace context.
221+
func ErrorfT(span *trace.Span, m string, args ...interface{}) error {
222+
err := &errorContext{
223+
err: fmt.Errorf(m, args...),
224+
sourceLocation: NewSourceLocation(wrappedFunctionCallDepth),
225+
}
226+
return annotate(err, span)
227+
}
228+
229+
// Wrap wraps an error fmt.Errorf with `%w` without formatting.
230+
func Wrap(e error, m string) error {
231+
err := &errorContext{
232+
err: fmt.Errorf("%s: %w", m, e),
233+
sourceLocation: NewSourceLocation(wrappedFunctionCallDepth),
234+
}
235+
return err
236+
}
237+
238+
// WrapT wraps an error with a span trace context.
239+
func WrapT(span *trace.Span, e error, m string) error {
240+
err := &errorContext{
241+
err: fmt.Errorf("%s: %w", m, e),
242+
sourceLocation: NewSourceLocation(wrappedFunctionCallDepth),
243+
}
244+
return annotate(err, span)
245+
}
246+
247+
// Wrap wraps fmt.Errorf with `%w` with formatting.
248+
func Wrapf(e error, f string, args ...interface{}) error {
249+
m := fmt.Sprintf(f, args...)
250+
err := &errorContext{
251+
err: fmt.Errorf("%s: %w", m, e),
252+
sourceLocation: NewSourceLocation(wrappedFunctionCallDepth),
253+
}
254+
return err
255+
}
256+
257+
// WrapfT is Wrapf with a trace context.
258+
func WrapfT(span *trace.Span, e error, f string, args ...interface{}) error {
259+
m := fmt.Sprintf(f, args...)
260+
err := &errorContext{
261+
err: fmt.Errorf("%s: %w", m, e),
262+
sourceLocation: NewSourceLocation(wrappedFunctionCallDepth),
263+
}
264+
return annotate(err, span)
265+
}
266+
267+
func annotate(e *errorContext, span *trace.Span) error {
268+
if span == nil {
269+
return e
270+
}
271+
272+
// Add the trace ID and span ID.
273+
ctx := span.SpanContext()
274+
e.traceContext = TraceContext{
275+
TraceID: ctx.TraceID.String(),
276+
SpanID: ctx.SpanID.String(),
277+
}
278+
279+
// Add OpenCensus span annotation.
280+
src := e.SourceLocation()
281+
span.Annotate(
282+
[]trace.Attribute{
283+
trace.StringAttribute("function", src.Function),
284+
trace.StringAttribute("file", src.File),
285+
trace.Int64Attribute("line", int64(src.Line)),
286+
trace.StringAttribute("version", src.Version),
287+
trace.StringAttribute("commit", src.Commit),
288+
trace.StringAttribute("branch", src.Branch),
289+
},
290+
"Error: "+e.Error(),
291+
)
292+
293+
// Generic error
294+
span.SetStatus(trace.Status{
295+
Code: trace.StatusCodeUnknown,
296+
})
297+
return e
298+
}

0 commit comments

Comments
 (0)