Having a bruh moment? No problem! Handle errors like a pro with bruh - the Go error handling library that simplifies error management and beautifies stack traces.
View Docs
·
Report Bug
·
Request Feature
Table of Contents
Having a bruh moment? Don't worry, bruh is here to help! bruh is a Go error handling library that makes it easy to deal with errors and print pretty stack traces. Simply create, wrap, and format your errors with all the details you need. Since it is designed as a drop-in replacement for Go's standard library errors
package, you don't even have to worry about making major changes to your code in order to get it working.
Features:
- includes stack traces (also included when captured by Sentry)
- offers custom error formatting
- allows to create custom errors
- acts as a drop-in replacement for Go's standard library
errors
package
go get github.com/aisbergg/go-bruh
Creating new errors with stack traces is done by calling bruh.New(msg string)
or Errorf(format string, args ...any)
:
package main
import (
"fmt"
"net/http"
"os"
"github.com/aisbergg/go-bruh/pkg/bruh"
)
// create a global error
var ErrInternalServer = bruh.New("error internal server")
func main() {
url := "https://foo-bar.local"
if _, err := Get(url); err != nil {
fmt.Fprintf(os.Stderr, "%+v\n", err)
os.Exit(1)
}
}
func Get(url string) (*http.Response, error) {
res, err := http.Get(url)
if err != nil {
// create a local error
return nil, bruh.New("request failed")
}
if res.StatusCode != http.StatusOK {
// create a local error using a format
return nil, bruh.Errorf("request failed with status code %d", res.StatusCode)
}
return res, nil
}
Outputs:
request failed
path/to/main.go:26 in main.Get
path/to/main.go:16 in main.main
Wrapping errors is not different than creating entirely new errors. You can use Wrap(err error, msg string)
or Wrapf(err error, format string, args ...any)
for this:
package main
import (
"fmt"
"net/http"
"os"
"github.com/aisbergg/go-bruh/pkg/bruh"
)
func main() {
url := "https://foo-bar.local"
if _, err := Get(url); err != nil {
err = bruh.Wrapf(err, "failed to fetch %s", url)
fmt.Fprintf(os.Stderr, "%+v\n", err)
os.Exit(1)
}
}
func Get(url string) (*http.Response, error) {
res, err := http.Get(url)
if err != nil {
// wrap previous error
return nil, bruh.Wrap(err, "request failed")
}
return res, nil
}
Outputs:
failed to fetch https://foo-bar.local
path/to/main.go:17 in main.main
request failed
path/to/main.go:27 in main.Get
path/to/main.go:16 in main.main
Get "https://foo-bar.local": dial tcp: lookup foo-bar.local: no such host
Errors can be formatted using the built-in formats or customized by creating a custom formatter. No matter what format you use, the basic usage is as such:
package main
import (
"fmt"
"github.com/aisbergg/go-bruh/pkg/bruh"
)
func main() {
err := foo()
// only message
ftdErr := fmt.Sprintf("%v", err)
// with trace
ftdErr = fmt.Sprintf("%+v", err)
// with custom formatter
ftdErr = bruh.ToCustomString(err, bruh.FormatWithCombinedTrace)
fmt.Println(ftdErr)
}
// external error
var extErr = fmt.Errorf("external")
func foo() error {
return bruh.Wrapf(bar(), "foo")
}
func bar() error {
return bruh.Wrapf(extErr, "bar")
}
Following formats are built-in:
-
bruh.FormatWithTrace
: FormatWithTrace is an error formatter that produces a trace containing a partial stack trace for each wrapped error. E.g.:<error1>: <file1>:<line1> in <function1> <file2>:<line2> in <function2> <fileN>:<lineN> in <functionN> <error2>: <file1>:<line1> in <function1> <file2>:<line2> in <function2> <fileN>:<lineN> in <functionN> <errorN>: <file1>:<line1> in <function1> <file2>:<line2> in <function2> <fileN>:<lineN> in <functionN>
-
bruh.FormatWithCombinedTrace
: FormatWithCombinedTrace is an error formatter that produces a single combined stack trace. E.g.:<error1>: <error2>: <errorN> <file1>:<line1> in <function1> <file2>:<line2> in <function2> <fileN>:<lineN> in <functionN>
-
bruh.FormatPythonTraceback
: FormatPythonTraceback is an error formatter that produces error traces similar to Python's tracebacks. E.g.:Traceback (most recent call last): File "<file3>", line <line3>, in <function3> File "<file2>", line <line2>, in <function2> File "<file1>", line <line1>, in <function1> <typeName1>: <error1> The above exception was the direct cause of the following exception: Traceback (most recent call last): File "<file3>", line <line3>, in <function3> File "<file2>", line <line2>, in <function2> File "<file1>", line <line1>, in <function1> <typeName2>: <error2>
You can customize the format by creating your own formatter. Check the json example on how to accomplish that.
Custom errors can be created based on the bruh standard error. The custom error will inherit the properties of the bruh error, such as the trace backs. Here is a short example:
Note: You can find a more detailed example in the examples directory
package main
import (
"fmt"
"time"
"github.com/aisbergg/go-bruh/pkg/bruh"
)
func main() {
err := fmt.Errorf("external error")
err = TEWrapf(err, "%s, what a day", "bruh")
if terr, ok := err.(*TimestampedError); ok {
fmt.Printf("%s: %s\n", terr.Timestamp().Format(time.RFC3339), terr.Error())
}
}
// TimestampedError represents the custom error. It embeds bruh.TraceableError
// and "inherits" its properties. This way, you can create your own custom types
// and add more properties as needed.
type TimestampedError struct {
bruh.TraceableError
timestamp time.Time
}
// TEWrapf creates the custom error. As you can see, it initializes the
// underlying bruh error using the `WrapfSkip` function. For each of the
// standard error creation function, there is a `Skip` equivalent. These
// initialize the bruh error and make sure, that this very function does not
// appear in the stack trace.
func TEWrapf(err error, format string, args ...any) error {
return &TimestampedError{
// skip is required to skip the current function and thus exclude this
// function from the stack trace
TraceableError: *bruh.WrapfSkip(err, 1, format, args...),
timestamp: time.Now(),
}
}
func (te *TimestampedError) Timestamp() time.Time {
return te.timestamp
}
Inside the benchmark
directory reside some comparable benchmarks that allow some performance comparison of bruh with other error handling libraries. The benchmarks can be executed by running make bench
. Here are my results:
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
BenchmarkWrap/pkg=bruh/layers=1-4 752203 1395 ns/op 648 B/op 5 allocs/op
BenchmarkWrap/pkg=pkgerrors/layers=1-4 814128 1465 ns/op 648 B/op 8 allocs/op
BenchmarkWrap/pkg=eris/layers=1-4 276560 3665 ns/op 2048 B/op 18 allocs/op
BenchmarkWrap/pkg=bruh/layers=10-4 148750 7318 ns/op 3600 B/op 32 allocs/op
BenchmarkWrap/pkg=pkgerrors/layers=10-4 125960 9040 ns/op 3744 B/op 53 allocs/op
BenchmarkWrap/pkg=eris/layers=10-4 60559 18084 ns/op 9537 B/op 81 allocs/op
BenchmarkWrap/pkg=bruh/layers=100-4 19662 62340 ns/op 33126 B/op 302 allocs/op
BenchmarkWrap/pkg=pkgerrors/layers=100-4 14648 84568 ns/op 34710 B/op 503 allocs/op
BenchmarkWrap/pkg=eris/layers=100-4 5730 178413 ns/op 84431 B/op 711 allocs/op
BenchmarkFormatWithoutTrace/pkg=bruh/layers=1-4 6034773 179.4 ns/op 1024 B/op 1 allocs/op
BenchmarkFormatWithoutTrace/pkg=pkgerrors/layers=1-4 7391492 153.3 ns/op 32 B/op 2 allocs/op
BenchmarkFormatWithoutTrace/pkg=eris/layers=1-4 779587 1530 ns/op 832 B/op 9 allocs/op
BenchmarkFormatWithoutTrace/pkg=bruh/layers=10-4 4275469 278.8 ns/op 1024 B/op 1 allocs/op
BenchmarkFormatWithoutTrace/pkg=pkgerrors/layers=10-4 1630375 700.7 ns/op 648 B/op 11 allocs/op
BenchmarkFormatWithoutTrace/pkg=eris/layers=10-4 183391 6237 ns/op 7136 B/op 54 allocs/op
BenchmarkFormatWithoutTrace/pkg=bruh/layers=100-4 822076 1259 ns/op 1024 B/op 1 allocs/op
BenchmarkFormatWithoutTrace/pkg=pkgerrors/layers=100-4 81950 14245 ns/op 48432 B/op 101 allocs/op
BenchmarkFormatWithoutTrace/pkg=eris/layers=100-4 10794 107690 ns/op 382554 B/op 504 allocs/op
BenchmarkFormatWithTrace/pkg=bruh/layers=1-4 348889 3600 ns/op 4745 B/op 9 allocs/op
BenchmarkFormatWithTrace/pkg=pkgerrors/layers=1-4 162422 6887 ns/op 1168 B/op 19 allocs/op
BenchmarkFormatWithTrace/pkg=eris/layers=1-4 198182 5363 ns/op 4457 B/op 51 allocs/op
BenchmarkFormatWithTrace/pkg=bruh/layers=10-4 69140 16209 ns/op 11529 B/op 27 allocs/op
BenchmarkFormatWithTrace/pkg=pkgerrors/layers=10-4 30410 46021 ns/op 6951 B/op 100 allocs/op
BenchmarkFormatWithTrace/pkg=eris/layers=10-4 64896 16679 ns/op 21166 B/op 159 allocs/op
BenchmarkFormatWithTrace/pkg=bruh/layers=100-4 7196 145970 ns/op 97930 B/op 209 allocs/op
BenchmarkFormatWithTrace/pkg=pkgerrors/layers=100-4 2107 568167 ns/op 64751 B/op 910 allocs/op
BenchmarkFormatWithTrace/pkg=eris/layers=100-4 3900 302192 ns/op 1044113 B/op 1241 allocs/op
If you have any suggestions, want to file a bug report or want to contribute to this project in some other way, please read the contribution guideline.
And don't forget to give this project a star 🌟! Thanks again!
Distributed under the MIT License. See LICENSE
for more information.
André Lehmann
- Email: aisberg@posteo.de
- GitHub
bruh was originally inspired by eris. I started of with eris's code base, but heavily modified it. bruh still borrows some tests and other places of the code might still resemble pieces of eris, so shoutout to the maintainers of eris!
The logo is a derivative of the logo by rfyiamcool. Sorry rfyiamcool that I butchered your gopher.