Skip to content

Commit 5f36dcb

Browse files
authored
Add section comparing errtrace to stack traces (#63)
A common question is how error traces differ from stack traces, so add an explicit section that covers this aspect, and move the HTTP example into this section, as it highlights the difference.
1 parent 4250159 commit 5f36dcb

File tree

1 file changed

+79
-54
lines changed

1 file changed

+79
-54
lines changed

README.md

Lines changed: 79 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
- [Introduction](#introduction)
1010
- [Features](#features)
11+
- [Comparison with stack traces](#comparison-with-stack-traces)
1112
- [Try it out](#try-it-out)
1213
- [Why is this useful](#why-is-this-useful)
1314
- [Installation](#installation)
@@ -49,6 +50,82 @@ This library is inspired by
4950
On popular 64-bit systems,
5051
errtrace is much faster than capturing a stack trace.
5152

53+
### Comparison with stack traces
54+
55+
With stack traces, caller information for the goroutine is
56+
captured once when the error is created.
57+
58+
In constrast, errtrace records the caller information incrementally,
59+
following the return path the error takes to get to the user.
60+
This approach works even if the error isn't propagated directly
61+
through function returns, and across goroutines.
62+
63+
Both approaches look similar when the error flows
64+
through function calls within the same goroutine,
65+
but can differ significantly when errors are passed
66+
outside of functions and across goroutines (e.g., channels).
67+
68+
Here's a real-world example that shows the benefits
69+
of errtrace tracing the return path
70+
by comparing a custom dial error returned for a HTTP request,
71+
which the net/http library uses a background goroutine for.
72+
73+
<details open>
74+
<summary>errtrace compared to a stack trace</summary>
75+
76+
<table>
77+
<thead>
78+
<tr><td>errtrace</td><td>stack trace</td></tr>
79+
</thead>
80+
<tbody>
81+
82+
<tr><td>
83+
84+
```
85+
Error: connect rate limited
86+
87+
braces.dev/errtrace_test.rateLimitDialer
88+
/path/to/errtrace/example_http_test.go:72
89+
braces.dev/errtrace_test.(*PackageStore).updateIndex
90+
/path/to/errtrace/example_http_test.go:59
91+
braces.dev/errtrace_test.(*PackageStore).Get
92+
/path/to/errtrace/example_http_test.go:49
93+
```
94+
95+
</td><td>
96+
97+
```
98+
Error: connect rate limited
99+
braces.dev/errtrace_test.rateLimitDialer
100+
/errtrace/example_stack_test.go:81
101+
net/http.(*Transport).dial
102+
/goroot/src/net/http/transport.go:1190
103+
net/http.(*Transport).dialConn
104+
/goroot/src/net/http/transport.go:1625
105+
net/http.(*Transport).dialConnFor
106+
/goroot/src/net/http/transport.go:1467
107+
runtime.goexit
108+
/goroot/src/runtime/asm_arm64.s:1197
109+
```
110+
111+
</td></tr>
112+
<tr>
113+
<td>errtrace reports the method that triggered the HTTP request</td>
114+
<td>stack trace shows details of how the HTTP client creates a connection</td>
115+
</tr>
116+
</tbody>
117+
</table>
118+
119+
</details>
120+
121+
errtrace also reduces the performance impact
122+
of capturing caller information for errors
123+
that are handled rather than returned to the user,
124+
as the information is captured incrementally.
125+
Stack traces pay a fixed cost to capture caller information
126+
even if the error is handled immediately by the caller
127+
close to where the error is created.
128+
52129
### Try it out
53130

54131
Try out errtrace with your own code:
@@ -102,10 +179,10 @@ example.com/myproject.CallerOfMyFunc
102179
[...]
103180
```
104181

105-
Some real world examples of errtrace in action:
182+
Here's a real-world example of errtrace in action:
106183

107184
<details>
108-
<summary>Example 1</summary>
185+
<summary>Example</summary>
109186

110187
```
111188
doc2go: parse file: /path/to/project/example/foo.go:3:1: expected declaration, found invalid
@@ -142,58 +219,6 @@ Note the some functions repeat in this trace
142219
because the functions are mutually recursive.
143220
</details>
144221

145-
<details open>
146-
<summary>Example 2</summary>
147-
148-
Realistic comparison of a
149-
stacktrace versus an error return trace
150-
for a custom dial error from the HTTP client,
151-
which happens on a background goroutine.
152-
153-
<table>
154-
<thead>
155-
<tr><td>errtrace</td><td>stacktrace</td></tr>
156-
</thead>
157-
<tbody>
158-
159-
<tr><td>
160-
161-
```
162-
Error: connect rate limited
163-
164-
braces.dev/errtrace_test.rateLimitDialer
165-
/path/to/errtrace/example_http_test.go:72
166-
braces.dev/errtrace_test.(*PackageStore).updateIndex
167-
/path/to/errtrace/example_http_test.go:59
168-
braces.dev/errtrace_test.(*PackageStore).Get
169-
/path/to/errtrace/example_http_test.go:49
170-
```
171-
172-
</td><td>
173-
174-
```
175-
Error: connect rate limited
176-
braces.dev/errtrace_test.rateLimitDialer
177-
/errtrace/example_stack_test.go:81
178-
net/http.(*Transport).dial
179-
/goroot/src/net/http/transport.go:1190
180-
net/http.(*Transport).dialConn
181-
/goroot/src/net/http/transport.go:1625
182-
net/http.(*Transport).dialConnFor
183-
/goroot/src/net/http/transport.go:1467
184-
runtime.goexit
185-
/goroot/src/runtime/asm_arm64.s:1197
186-
```
187-
188-
</td></tr>
189-
<tr>
190-
<td>errtrace reports the method that triggered the HTTP request</td>
191-
<td>stacktrace shows details of how the HTTP client creates a connection</td>
192-
</tr>
193-
</tbody>
194-
</table>
195-
196-
</details>
197222

198223
### Why is this useful?
199224

0 commit comments

Comments
 (0)