https://github.com/bracesdev/errtrace Skip to content Toggle navigation Sign up * Product + Actions Automate any workflow + Packages Host and manage packages + Security Find and fix vulnerabilities + Codespaces Instant dev environments + Copilot Write better code with AI + Code review Manage code changes + Issues Plan and track work + Discussions Collaborate outside of code Explore + All features + Documentation + GitHub Skills + Blog * Solutions For + Enterprise + Teams + Startups + Education By Solution + CI/CD & Automation + DevOps + DevSecOps Resources + Learning Pathways + White papers, Ebooks, Webinars + Customer Stories + Partners * Open Source + GitHub Sponsors Fund open source developers + The ReadME Project GitHub community articles Repositories + Topics + Trending + Collections * Pricing Search or jump to... Search code, repositories, users, issues, pull requests... Search [ ] Clear Search syntax tips Provide feedback We read every piece of feedback, and take your input very seriously. [ ] [ ] Include my email address so I can be contacted Cancel Submit feedback Saved searches Use saved searches to filter your results more quickly Name [ ] Query [ ] To see all available qualifiers, see our documentation. Cancel Create saved search Sign in Sign up You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session. Dismiss alert {{ message }} bracesdev / errtrace Public * Notifications * Fork 0 * Star 132 An alternative to stack traces for your Go errors pkg.go.dev/braces.dev/errtrace License BSD-3-Clause license 132 stars 0 forks Activity Star Notifications * Code * Issues 8 * Pull requests 0 * Discussions * Actions * Security * Insights Additional navigation options * Code * Issues * Pull requests * Discussions * Actions * Security * Insights bracesdev/errtrace This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository. main Switch branches/tags [ ] Branches Tags Could not load branches Nothing to show {{ refName }} default View all branches Could not load tags Nothing to show {{ refName }} default View all tags Name already in use A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch? Cancel Create 4 branches 2 tags Code * Local * Codespaces * Clone HTTPS GitHub CLI [https://github.com/b] Use Git or checkout with SVN using the web URL. [gh repo clone braces] Work fast with our official CLI. Learn more about the CLI. * Open with GitHub Desktop * Download ZIP Sign In Required Please sign in to use Codespaces. Launching GitHub Desktop If nothing happens, download GitHub Desktop and try again. Launching GitHub Desktop If nothing happens, download GitHub Desktop and try again. Launching Xcode If nothing happens, download Xcode and try again. Launching Visual Studio Code Your codespace will open once ready. There was a problem preparing your codespace, please try again. Latest commit @abhinav abhinav cmd/errtrace: Support opt-out with //errtrace:skip (#57) ... dbbf10f Nov 29, 2023 cmd/errtrace: Support opt-out with //errtrace:skip (#57) Affected lines may add `//errtrace:skip` to opt-out of being instrumented. This is necessary for types implementing `io.Reader`; they must return `io.EOF`, not `fmt.Errorf("%w", io.EOF)`, or functions like `io.ReadAll` will misbehave[^1]. [^1]: https://cs.opensource.google/go/go/+/refs/tags/go1.21.4:src/io/io.go;l=707 dbbf10f Git stats * 57 commits Files Permalink Failed to load latest commit information. Type Name Latest commit message Commit time .github/workflows CI: explicitly specify go version for windows/macos (#55) November 28, 2023 19:24 assets go.mod: Drop constraint to 1.20 (#51) November 28, 2023 18:29 benchext go.mod: Drop constraint to 1.20 (#51) November 28, 2023 18:29 cmd/errtrace cmd/errtrace: Support opt-out with //errtrace:skip (#57) November 29, 2023 04:56 internal Fix tests for windows, and run CI on windows (#53) November 28, 2023 19:14 .gitignore ci: Track coverage with codecov.io (#49) November 28, 2023 17:49 .golangci.yml ci: Set up a lint job (#32) November 25, 2023 13:23 CHANGELOG.md cmd/errtrace: Support opt-out with //errtrace:skip (#57) November 29, 2023 04:56 LICENSE Initial commit November 3, 2023 08:19 Makefile Fix tests for windows, and run CI on windows (#53) November 28, 2023 19:14 README.md cmd/errtrace: Support opt-out with //errtrace:skip (#57) November 29, 2023 04:56 arena.go Use sync.Pool to allocate arena slabs and reduce contention (#9) November 9, 2023 07:50 codecov.yml ci: Track coverage with codecov.io (#49) November 28, 2023 17:49 errors.go Add more godoc (#43) November 28, 2023 08:21 errtrace.go Add more godoc (#43) November 28, 2023 08:21 errtrace_line_test.go Redo format: include errors, reverse stacks, support multi-errors (# 27) November 25, 2023 13:02 errtrace_test.go Include error trace when formatted with %+v (#28) November 23, 2023 21:37 example_http_test.go Add errtrace vs stack example to tests and README (#41) November 28, 2023 07:52 example_trace_test.go Redo format: include errors, reverse stacks, support multi-errors (# 27) November 25, 2023 13:02 example_tree_test.go Update trace example to show order of errors is maintained (#33) November 26, 2023 10:11 go.mod go.mod: Drop constraint to 1.20 (#51) November 28, 2023 18:29 tree.go Redo format: include errors, reverse stacks, support multi-errors (# 27) November 25, 2023 13:02 tree_go121.go Redo format: include errors, reverse stacks, support multi-errors (# 27) November 25, 2023 13:02 tree_pre_go121.go Redo format: include errors, reverse stacks, support multi-errors (# 27) November 25, 2023 13:02 tree_test.go Redo format: include errors, reverse stacks, support multi-errors (# 27) November 25, 2023 13:02 wrap.go Add noinline to callers of GetCaller, consistent comments November 22, 2023 18:20 View code [ ] errtrace Introduction Features Try it out Why is this useful? Installation Usage Manual instrumentation Automatic instrumentation Opting-out during automatic instrumentation Performance Caveats Error wrapping Linting Safety Supported systems Contributing Acknowledgements License README.md errtrace errtrace logo CI Go Reference codecov * Introduction + Features + Try it out + Why is this useful * Installation * Usage + Manual instrumentation + Automatic instrumentation * Performance * Caveats + Error wrapping + Safety * Contributing * Acknowledgements * License Introduction errtrace is an experimental package to trace an error's return path -- the return trace -- through a Go program. Where a stack trace tracks the code path that led to an error, a return trace tracks the code path that the error took to get to the user. Often these are the same path, but in Go they can diverge, since errors are values that can be transported across goroutines (e.g. with channels). When that happens, a return trace can be more useful than a stack trace. This library is inspired by Zig's error return traces. Features * Lightweight errtrace brings no other runtime dependencies with it. * Simple The library API is simple, straightforward, and idiomatic. * Easy The errtrace CLI will automatically instrument your code. * Fast On popular 64-bit systems, errtrace is much faster than capturing a stack trace. Try it out Try out errtrace with your own code: 1. Install the CLI. go install braces.dev/errtrace/cmd/errtrace@latest 2. Switch to your Git repository and instrument your code. git ls-files -- '*.go' | xargs errtrace -w 3. Let go mod tidy install the errtrace Go module for you. go mod tidy 4. Run your tests to ensure everything still works. You may see failures if you're comparing errors with == on critical paths or if you're type-casting errors directly. See Error wrapping for more details. go test ./... 5. Print return traces for errors in your code. To do this, you can use the errtrace.FormatString function or format the error with %+v in fmt.Printf-style functions. if err != nil { fmt.Fprintf(os.Stderr, "%+v", err) } Return traces printed by errtrace will include the error message and the path the error took until it was printed. The output will look roughly like this: error message example.com/myproject.MyFunc /home/user/myproject/myfile.go:123 example.com/myproject.CallerOfMyFunc /home/user/myproject/another_file.go:456 [...] Some real world examples of errtrace in action: Example 1 doc2go: parse file: /path/to/project/example/foo.go:3:1: expected declaration, found invalid go.abhg.dev/doc2go/internal/gosrc.parseFiles /path/to/project/internal/gosrc/parser.go:85 go.abhg.dev/doc2go/internal/gosrc.(*Parser).ParsePackage /path/to/project/internal/gosrc/parser.go:44 main.(*Generator).renderPackage /path/to/project/generate.go:193 main.(*Generator).renderTree /path/to/project/generate.go:141 main.(*Generator).renderTrees /path/to/project/generate.go:118 main.(*Generator).renderPackageIndex /path/to/project/generate.go:149 main.(*Generator).renderTree /path/to/project/generate.go:137 main.(*Generator).renderTrees /path/to/project/generate.go:118 main.(*Generator).renderPackageIndex /path/to/project/generate.go:149 main.(*Generator).renderTree /path/to/project/generate.go:137 main.(*Generator).renderTrees /path/to/project/generate.go:118 main.(*Generator).Generate /path/to/project/generate.go:110 main.(*mainCmd).run /path/to/project/main.go:199 Note the some functions repeat in this trace because the functions are mutually recursive. Example 2 Realistic comparison of a stacktrace versus an error return trace for a custom dial error from the HTTP client, which happens on a background goroutine. errtrace stacktrace Error: connect rate limited Error: connect rate limited braces.dev/errtrace_test.rateLimitDialer /errtrace/example_stack_test.go:81 braces.dev/errtrace_test.rateLimitDialer net/http.(*Transport).dial /path/to/errtrace/example_http_test.go:72 /goroot/src/net/http/transport.go:1190 braces.dev/errtrace_test.(*PackageStore).updateIndex net/http.(*Transport).dialConn /path/to/errtrace/example_http_test.go:59 /goroot/src/net/http/transport.go:1625 braces.dev/errtrace_test.(*PackageStore).Get net/http.(*Transport).dialConnFor /path/to/errtrace/example_http_test.go:49 /goroot/src/net/http/transport.go:1467 runtime.goexit /goroot/src/runtime/asm_arm64.s:1197 errtrace reports the method that triggered the HTTP stacktrace shows details of how the HTTP request client creates a connection Why is this useful? In Go, errors are values. This means that an error can be passed around like any other value. You can store it in a struct, pass it through a channel, etc. This level of flexibility is great, but it can also make it difficult to track down the source of an error. A stack trace stored in an error -- recorded at the error site -- becomes less useful as the error moves through the program. When it's eventually surfaced to the user, we've lost a lot of context about its origin. With errtrace, we instead record the path the program took from the error site to get to the user -- the return trace. Not only can this be more useful than a stack trace, it tends to be much faster and more lightweight as well. Installation Install errtrace with Go modules: go get braces.dev/errtrace@latest If you want to use the CLI, use go install. go install braces.dev/errtrace/cmd/errtrace@latest Usage errtrace offers the following modes of usage: * Manual instrumentation * Automatic instrumentation Manual instrumentation import "braces.dev/errtrace" Under manual instrumentation, you're expected to import errtrace, and wrap errors at all return sites like so: // ... if err != nil { return errtrace.Wrap(err) } Example Given a function like the following: func writeToFile(path string, src io.Reader) error { dst, err := os.Create(path) if err != nil { return err } defer dst.Close() if _, err := io.Copy(dst, src); err != nil { return err } return nil } With errtrace, you'd change it to: func writeToFile(path string, src io.Reader) error { dst, err := os.Create(path) if err != nil { return errtrace.Wrap(err) } defer dst.Close() if _, err := io.Copy(dst, src); err != nil { return errtrace.Wrap(err) } return nil } It's important that the errtrace.Wrap function is called inside the same function that's actually returning the error. A helper function will not suffice. Automatic instrumentation If manual instrumentation is too much work (we agree), we've included a tool that will automatically instrument all your code with errtrace. First, install the tool. Then, run it on your code: errtrace -w path/to/file.go path/to/another/file.go To run it on all Go files in your project, if you use Git, run the following command on a Unix-like system: git ls-files -- '*.go' | xargs errtrace -w errtrace can be set be setup as a custom formatter in your editor, similar to gofmt or goimports. Opting-out during automatic instrumentation If you're relying on automatic instrumentation and want to ignore specific lines from being instrumented, you can add a comment in one of the following forms on relevant lines: //errtrace:skip //errtrace:skip explanation This can be especially useful if the returned error has to match another error exactly because the caller still uses ==. For example, if you're implementing io.Reader, you need to return io.EOF when you reach the end of the input. Wrapping it will cause functions like io.ReadAll to misbehave. type myReader struct{/* ... */} func (*myReader) Read(bs []byte) (int, error) { // ... return 0, io.EOF //errtrace:skip (io.Reader requires io.EOF) } Performance errtrace is designed to have very low overhead on supported systems. Benchmark results for linux/amd64 on an Intel Core i5-13600 (best of 10): BenchmarkFmtErrorf 11574928 103.5 ns/op 40 B/op 2 allocs/op # default build, uses Go assembly. BenchmarkWrap 78173496 14.70 ns/op 24 B/op 0 allocs/op # build with -tags safe to avoid assembly. BenchmarkWrap 5958579 198.5 ns/op 24 B/op 0 allocs/op # benchext compares capturing stacks using pkg/errors vs errtrace # both tests capture ~10 frames, BenchmarkErrtrace 6388651 188.4 ns/op 280 B/op 1 allocs/op BenchmarkPkgErrors 1673145 716.8 ns/op 304 B/op 3 allocs/op Stack traces have a large initial cost, while errtrace scales with each frame that an error is returned through. Caveats Error wrapping errtrace operates by wrapping your errors to add caller information. As a result of this, error comparisons and type-casting may not work as expected. You can no longer use == to compare errors, or type-cast them directly. You must use the standard library's errors.Is and errors.As functions. For example, if you have a function readFile that wraps an io.EOF error with errtrace: Matching errors err := readFile() // returns errtrace.Wrap(io.EOF) // This will not work. fmt.Println(err == io.EOF) // false // Use errors.Is instead. fmt.Println(errors.Is(err, io.EOF)) // true Similarly, if you have a function runCmd that wraps an exec.ExitError error with errtrace: Type-casting errors err := runCmd() // returns errtrace.Wrap(&exec.ExitError{...}) // This will not work. exitErr, ok := err.(*exec.ExitError) // ok = false // Use errors.As instead. var exitErr *exec.ExitError ok := errors.As(err, &exitErr) // ok = true Linting You can use go-errorlint to find places in your code where you're comparing errors with == instead of using errors.Is or type-casting them directly instead of using errors.As. Safety To achieve the performance above on supported systems, errtrace makes use of unsafe operations using Go assembly to read the caller information directly from the stack. This is part of the reason why we have the disclaimer on top. errtrace includes an opt-in safe mode that drops these unsafe operations in exchange for poorer performance. To opt into safe mode, use the safe build tag when compiling code that uses errtrace. go build -tags safe Supported systems errtrace's unsafe operations are currently implemented for GOARCH= amd64 and GOARCH=arm64 only. Other systems are supported but they will use safe mode, which is slower. Contributions to support unsafe mode for other architectures are welcome. Contributing Contributions are welcome. However, we ask that before contributing new features, you open an issue to discuss the feature with us. Acknowledgements The idea of tracing return paths instead of stack traces comes from Zig's error return traces. License This software is made available under the BSD3 license. See LICENSE file for details. About An alternative to stack traces for your Go errors pkg.go.dev/braces.dev/errtrace Topics golang Resources Readme License BSD-3-Clause license Activity Stars 132 stars Watchers 3 watching Forks 0 forks Report repository Releases 2 v0.1.1 Latest Nov 29, 2023 + 1 release Contributors 3 * @abhinav abhinav Abhinav Gupta * @prashantv prashantv Prashant Varanasi * @akshayjshah akshayjshah Akshay Shah Languages * Go 97.5% * Makefile 1.5% * Assembly 1.0% Footer (c) 2023 GitHub, Inc. Footer navigation * Terms * Privacy * Security * Status * Docs * Contact You can't perform that action at this time.