|
8 | 8 |
|
9 | 9 | - [Introduction](#introduction) |
10 | 10 | - [Features](#features) |
| 11 | + - [Comparison with stack traces](#comparison-with-stack-traces) |
11 | 12 | - [Try it out](#try-it-out) |
12 | 13 | - [Why is this useful](#why-is-this-useful) |
13 | 14 | - [Installation](#installation) |
@@ -49,6 +50,82 @@ This library is inspired by |
49 | 50 | On popular 64-bit systems, |
50 | 51 | errtrace is much faster than capturing a stack trace. |
51 | 52 |
|
| 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 | + |
52 | 129 | ### Try it out |
53 | 130 |
|
54 | 131 | Try out errtrace with your own code: |
@@ -102,10 +179,10 @@ example.com/myproject.CallerOfMyFunc |
102 | 179 | [...] |
103 | 180 | ``` |
104 | 181 |
|
105 | | -Some real world examples of errtrace in action: |
| 182 | +Here's a real-world example of errtrace in action: |
106 | 183 |
|
107 | 184 | <details> |
108 | | -<summary>Example 1</summary> |
| 185 | +<summary>Example</summary> |
109 | 186 |
|
110 | 187 | ``` |
111 | 188 | 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 |
142 | 219 | because the functions are mutually recursive. |
143 | 220 | </details> |
144 | 221 |
|
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> |
197 | 222 |
|
198 | 223 | ### Why is this useful? |
199 | 224 |
|
|
0 commit comments