Awesome Go

go-bruh

CategoryError Handling
SubcategoryError Handling
Stars3

Error handling with stack traces, custom formatting, and observability integration

About go-bruh

This large README is shown as a plain-text preview. Read the full README at the source

<a name="readme-top"></a>

[![GoDoc](https://pkg.go.dev/badge/github.com/aisbergg/go-bruh)](https://pkg.go.dev/github.com/aisbergg/go-bruh/pkg/bruh)
[![Coverage Status](https://codecov.io/gh/aisbergg/go-bruh/branch/main/graph/badge.svg)](https://codecov.io/gh/aisbergg/go-bruh)
[![CodeQL](https://github.com/aisbergg/go-bruh/actions/workflows/codeql.yml/badge.svg)](https://github.com/aisbergg/go-bruh/actions/workflows/codeql.yml)
[![License](https://img.shields.io/github/license/aisbergg/go-bruh)](https://pkg.go.dev/github.com/aisbergg/go-bruh)
[![LinkedIn](https://img.shields.io/badge/-LinkedIn-green.svg?logo=linkedin&colorB=555)](https://www.linkedin.com/in/andre-lehmann-97408221a/)

<br />
<br />
<div align="center">
  <a href="https://github.com/aisbergg/go-bruh">
    <img src="assets/logo.png" alt="Logo" width="256" height="256">
  </a>

  <h2 align="center"><b>bruh - Error Handling</b></h2>

  <p align="center">
    Having a bruh moment? No problem! Handle errors like a pro with <i>bruh</i> - the Go error handling library that simplifies error management and beautifies stack traces.
    <br />
    <br />
    <a href="https://pkg.go.dev/github.com/aisbergg/go-bruh/pkg/bruh">View Docs</a>
    ·
    <a href="https://github.com/aisbergg/go-bruh/issues">Report Bug</a>
    ·
    <a href="https://github.com/aisbergg/go-bruh/issues">Request Feature</a>
  </p>
</div>

<details open="open">
  <summary>Table of Contents</summary>

- [About](#about)
- [Installation](#installation)
- [Usage](#usage)
    - [Creating and Wrapping Errors](#creating-and-wrapping-errors)
    - [Creating from Panic](#creating-from-panic)
    - [Creating Custom Errors](#creating-custom-errors)
    - [Formatting Errors](#formatting-errors)
        - [Built-in Formats](#built-in-formats)
            - [`BruhFormatter`](#bruhformatter)
            - [`BruhFancyFormatter(colored, sourced)`](#bruhfancyformattercolored-sourced)
            - [`BruhStackedFormatter`](#bruhstackedformatter)
            - [`BruhStackedFancyFormatter(colored, sourced, typed)`](#bruhstackedfancyformattercolored-sourced-typed)
            - [`GoPanicFormatter`](#gopanicformatter)
            - [`JavaStackTraceFormatter`](#javastacktraceformatter)
            - [`PythonTracebackFormatter`](#pythontracebackformatter)
        - [Custom Formats](#custom-formats)
    - [Stack Depth](#stack-depth)
    - [Stacktrace Without Bruh](#stacktrace-without-bruh)
    - [Integrations](#integrations)
        - [Sentry](#sentry)
        - [OTEL](#otel)
        - [slog](#slog)
    - [Multi Error](#multi-error)
    - [Context Error](#context-error)
- [Benchmark](#benchmark)
- [Contributing](#contributing)
- [License](#license)
- [Contact](#contact)

</details>

## About

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.

**Features:**

- Captures stack traces by default and is compatible with [Sentry](https://sentry.io) and [BugSnag](https://www.bugsnag.com).
- Supports sleek, flexible formatting with built-in formatters plus custom formatters (for example JSON output).
- Integrates with `slog` via [`ctxslog`](#slog) for structured logging of error context and tags.
- Integrates with OpenTelemetry via [`ctxotel`](#otel) for recording rich error metadata and stack traces on spans.
- Offers better performance compared to other error libraries that support stack traces.

![](assets/examples.svg)

I like the idea of error handling as a form of storytelling as advocated in [this article](https://preslav.me/2023/04/14/golang-error-handling-is-a-form-of-storytelling/). I urge you to read it, but here is a short summary: If you put some effort in creating and wrapping your errors you can actually tell a readable story. A story about what went wrong and what your code was doing when the error occurred. Keep every part of the concatenated error message concise and descriptive. Use present tense and say what the code was attempting to do in that moment of time. Avoid words like "failed", "cannot", "won’t", etc. - it is clear to the reader of the log message that if it occurred, something did not happen. Here is an example: `getting user: connecting to the DB: connection refused`

Bruh, whats the deal with the name? Sure, it might be a teeny weeny bit overused and annoying, but still better than the next pun on "errors". Also, when I first saw that someone created a joke PR on the Linux kernel [exchanging "kernel panic" with "bruh moment"](https://web.archive.org/web/20260115134912/https://github.com/torvalds/linux/pull/684) I chuckled and it got stuck with me. There you have it.

<p align="right"><a href="#readme-top"><b>back to top ⇧</b></a></p>

## Installation

```sh
go get github.com/aisbergg/go-bruh
```

<p align="right"><a href="#readme-top"><b>back to top ⇧</b></a></p>

## Usage

**Synopsis:**

```go
package main

import (
	"fmt"

	"github.com/aisbergg/go-bruh/pkg/bruh"
)

func main() {
	// error creation
	err := bruh.New("unexpected end of file")
	// error wrapping
	err = bruh.Wrapf(err, "reading file '%s'", "example.txt")
	// using a custom error type
	err = WrapUserError(err, "12345", "retrieving user data")
	// recovering a panic
	err = RecoverFromPanic()

	// error formatting
	fmt.Println(bruh.String(err))
	// using a different format
	fmt.Println(bruh.StringFormat(err, bruh.GoPanicFormatter))

	// testing for specific errors
	var userErr *UserError
	if bruh.As(err, &userErr) {
		fmt.Printf("User ID: %s\n", userErr.UserID)
	}
}

type UserError struct {
	bruh.Err
	UserID string
}

func WrapUserError(err error, userID, message string) *UserError {
	return &UserError{
		Err:    *bruh.WrapSkip(err, 1, message),
		UserID: userID,
	}
}

func RecoverFromPanic() (err error) {
	defer func() {
		if r := recover(); r != nil {
			// extracts the message and stack trace from the panic
			err = bruh.NewFromPanic(r)
		}
	}()
	panic("something went wrong")
}
```

### Creating and Wrapping Errors

Creating new errors with stack traces is done by calling [`bruh.New(msg string)`](https://pkg.go.dev/github.com/aisbergg/go-bruh/pkg/bruh#New) or [`Errorf(format string, args ...any)`](https://pkg.go.dev/github.com/aisbergg/go-bruh/pkg/bruh#Errorf).

To wrap existing errors and add more context you can use [`Wrap(err error, msg string)`](https://pkg.go.dev/github.com/aisbergg/go-bruh/pkg/bruh#Wrap) or [`Wrapf(err error, format string, args ...any)`](https://pkg.go.dev/github.com/aisbergg/go-bruh/pkg/bruh#Wrapf).

```golang
package main

import (
	"fmt"
	"net/http"
	"os"
	"time"

	"github.com/aisbergg/go-bruh/pkg/bruh"
)

// define a global error; you can also create root errors with `bruh.New`
// elsewhere as needed
var ErrInternalServer = bruh.New("error internal server")

func main() {
	url := "https://foo-bar.local/resource/dog.jpg"
	if _, err := Get(url); err != nil {
		err = bruh.Wrap(err, "getting an image of a dog")
		fmt.Fprintf(os.Stderr, "%s\n", bruh.String(err))
		os.Exit(1)
	}
}

func Get(url string) (*http.Response, error) {
	client := http.Client{Timeout: 300 * time.Millisecond}
	res, err := client.Get(url)
	if err == nil && res.StatusCode != http.StatusOK {
		// create a root error with a formatted message
		err = bruh.Errorf("GET \"%s\" failed with status code %d", url, res.StatusCode)
	}
	if err != nil {
		// wrap the error and add context
		return nil, bruh.Wrap(err, "requesting resource")
	}
	return res, nil
}
```

Output:

```plaintext
getting an image of a dog: requesting remote resource: Get "https://foo-bar.local/resource/dog.jpg": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
    at main.Get (.../examples/readme/create_and_wrap/main.go:36)
    at main.main (.../examples/readme/create_and_wrap/main.go:18)
    at main.main (.../examples/readme/create_and_wrap/main.go:19)
```

<p align="right"><a href="#readme-top"><b>back to top ⇧</b></a></p>

### Creating from Panic

Errors with stack traces can also be created from a panic by calling recover and passing its value to [`bruh.NewFromPanic(panicValue any)`](https://pkg.go.dev/github.com/aisbergg/go-bruh/pkg/bruh#NewFromPanic).

```golang
package main

import (
	"fmt"

	"github.com/aisbergg/go-bruh/pkg/bruh"
)

func main() {
	defer func() {
		if r := recover(); r != nil {
			err := bruh.NewFromPanic(r)
			fmt.Printf("%s\n", bruh.StringFormat(err, bruh.BruhFormatter))
		}
	}()

	fmt.Printf("result: %d\n", Divide(10, 0))

	// this will never be reached because the panic will be caught by the defer
	fmt.Println("done")
}

func Divide(a, b int) int {
	if b == 0 {
		panic("division by zero")
	}
	return a / b
}
```

Output:

```plaintext
division by zero
    at main.Divide (.../examples/readme/create_from_panic/main.go:25)
    at main.main (.../examples/readme/create_from_panic/main.go:17)
```

<p align="right"><a href="#readme-top"><b>back to top ⇧</b></a></p>

### Creating Custom Errors

Custom errors can be created based on the bruh standard error leveraging struct embedding. The custom error will "inherit" the properties of the bruh error and automatically be decorated with a stack trace. Here is an example:

```go
package main

import (
	"fmt"
	"time"

	"github.com/aisbergg/go-bruh/pkg/bruh"
)

func main() {
	err := fmt.Errorf("external error")
	err = Wrapf(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.Err]
// and "inherits" its properties. This way, you can create your own custom types
// and add more properties as needed.
type TimestampedError struct {
	bruh.Err
	timestamp time.Time
}

// Wrapf creates the custom error. It initializes the embedded bruh error using
// the `WrapfSkip` function. For each standard error creation function, there is
// a corresponding `Skip` variant. These ensure that the current function is
// excluded from the stack trace, so it does not appear in the error output.
func Wrapf(err error, format string, args ...any) error {
    if err == nil {
		return nil
	}
	return &TimestampedError{
		// skip is required to skip the current function and thus exclude this
		// function from the stack trace
		Error:     *bruh.WrapfSkip(err, 1, format, args...),
		timestamp: time.Now(),
	}
}

func (te *TimestampedError) Timestamp() time.Time {
	return te.timestamp
}
```

> You can find a full example here [timestamped_error.go](examples/custom_error/timestamped_error.go)

<p align="right"><a href="#readme-top"><b>back to top ⇧</b></a></p>

### Formatting Errors

To format errors as a string you can use the built-in formats provide custom ones. No matter what format you use, the basic usage is as follows:

```go
package main

import (
	"fmt"

	"github.com/aisbergg/go-bruh/pkg/bruh"
)

func main() {
	err := foo()

	// message only
	f := bruh.Message(err)
    // or simply
    f = err.Error()

	// with trace (default format)
	f = bruh.String(err)

	// with custom format
	f = bruh.StringFormat(err, bruh.BruhStackedFormatter)

	fmt.Println(f)
}

// some external error
var extErr = fmt.Errorf("external")

func foo() error {
	return bruh.Wrapf(bar(), "foo")
}

func bar() error {
	return bruh.Wrapf(extErr, "bar")
}
```

Output:

```plaintext
foo
    at main.foo (.../examples/readme/format/main.go:30)
    at main.main (.../examples/readme/format/main.go:10)
bar
    at main.bar (.../examples/readme/format/main.go:34)
    at main.foo (.../examples/readme/format/main.go:30)
external
```

#### Built-in Formats

Following formats are built-in:

##### `BruhFormatter`

```plaintext
configuring application: decoding data: reading file 'example.json': unexpected EOF
    at main.readFile (readme/formats_showcase/main.go:67)
    at main.decodingData (readme/formats_showcase/main.go:57)
    at main.decodingData (readme/formats_showcase/main.go:59)
    at main.configure (readme/formats_showcase/main.go:49)
    at main.configure (readme/formats_showcase/main.go:51)
    at main.main (readme/formats_showcase/main.go:14)
```

##### `BruhFancyFormatter(colored, sourced)`

A variant of `bruh.BruhFormatter` that can include a source code snippet for each error location and add ANSI coloring.

Source code snippets can only be included, if the source code is available in the current working directory. If the source code is not available, the formatter will fall back to the default `bruh.BruhFormatter`.

```plaintext
configuring application: decoding data: reading file 'example.json': unexpected EOF

at main.readFile (readme/formats_showcase/main.go:67)
    65│    err := io.ErrUnexpectedEOF
    66│    if err != nil {
  → 67│        return bruh.Wrapf(err, "reading file '%s'", path)
    68│    }
    69│    return nil
at main.decodingData (readme/formats_showcase/main.go:57)
    55│
    56│    func decodingData() error {
  → 57│        err := readFile("example.json")
    58│        if err != nil {
    59│            return bruh.Wrap(err, "decoding data")
at main.decodingData (readme/formats_showcase/main.go:59)
    57│    err := readFile("example.json")
    58│    if err != nil {
  → 59│        return bruh.Wrap(err, "decoding data")
    60│    }
    61│    return nil
at main.configure (readme/formats_showcase/main.go:49)
    47│
    48│    func configure() error {
  → 49│        err := decodingData()
    50│        if err != nil {
    51│            return bruh.Wrap(err, "configuring application")
at main.configure (readme/formats_showcase/main.go:51)
    49│    err := decodingData()
    50│    if err != nil {
  → 51│        return bruh.Wrap(err, "configuring application")
    52│    }
    53│    return nil
at main.main (readme/formats_showcase/main.go:14)
    12│
    13│    func main() {
  → 14│        err := configure()
    15│
    16│        formats := []bruh.Formatter{
```

##### `BruhStackedFormatter`

```plaintext
configuring application
    at main.configure (readme/formats_showcase/main.go:51)
    at main.main (readme/formats_showcase/main.go:14)
decoding data
    at main.decodingData (readme/formats_showcase/main.go:59)
    at main.configure (readme/formats_showcase/main.go:49)
reading file 'example.json'
    at main.readFile (readme/formats_showcase/main.go:67)
    at main.decodingData (readme/formats_showcase/main.go:57)
unexpected EOF
```

##### `BruhStackedFancyFormatter(colored, sourced, typed)`

A variant of `bruh.BruhStackedFormatter` that can include a source code snippet for each error location, display error type information, and add ANSI coloring.

Source code snippets can only be included, if the source code is available in the current working directory. If the source code is not available, the formatter will fall back to the default `bruh.BruhFormatter`.

```plaintext
configuring application

at main.configure (readme/formats_showcase/main.go:51)
    49│    err := decodingData()
    50│    if err != nil {
  → 51│        return bruh.Wrap(err, "configuring application")
    52│    }
    53│    return nil
at main.main (readme/formats_showcase/main.go:14)
    12│
    13│    func main() {
  → 14│        err := configure()
    15│
    16│        formats := []bruh.Formatter{
decoding data

at main.decodingData (readme/formats_showcase/main.go:59)
    57│    err := readFile("example.json")
    58│    if err != nil {
  → 59│        return bruh.Wrap(err, "decoding data")
    60│    }
    61│    return nil
at main.configure (readme/formats_showcase/main.go:49)
    47│
    48│    func configure() error {
  → 49│        err := decodingData()
    50│        if err != nil {
    51│            return bruh.Wrap(err, "configuring application")
reading file 'example.json'

at main.readFile (readme/formats_showcase/main.go:67)
    65│    err := io.ErrUnexpectedEOF
    66│    if err != nil {
  → 67│        return bruh.Wrapf(err, "reading file '%s'", path)
    68│    }
    69│    return nil
at main.decodingData (readme/formats_showcase/main.go:57)
    55│
    56│    func decodingData() error {
  → 57│        err := readFile("example.json")
    58│        if err != nil {
    59│            return bruh.Wrap(err, "decoding data")
unexpected EOF
```

##### `GoPanicFormatter`

```plaintext
configuring application: decoding data: reading file 'example.json': unexpected EOF

main.readFile()
	readme/formats_showcase/main.go:67 +0x4b33a5
main.decodingData()
	readme/formats_showcase/main.go:57 +0x4b3342
main.decodingData()
	readme/formats_showcase/main.go:59 +0x4b33c5
main.configure()
	readme/formats_showcase/main.go:49 +0x4b32d2
main.configure()
	readme/formats_showcase/main.go:51 +0x4b32ee
main.main()
	readme/formats_showcase/main.go:14 +0x4b2f7e
```

##### `JavaStackTraceFormatter`

```plaintext
*bruh.Err: configuring application
    at main.configure (readme/formats_showcase/main.go:51)
    at main.main (readme/formats_showcase/main.go:14)
Caused by: *bruh.Err: decoding data
    at main.decodingData (readme/formats_showcase/main.go:59)
    at main.configure (readme/formats_showcase/main.go:49)
Caused by: *bruh.Err: reading file 'example.json'
    at main.readFile (readme/formats_showcase/main.go:67)
    at main.decodingData (readme/formats_showcase/main.go:57)
Caused by: *errors.errorString: unexpected EOF
```

##### `PythonTracebackFormatter`

```plaintext
*errors.errorString: unexpected EOF

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "readme/formats_showcase/main.go", line 57, in main.decodingData
  File "readme/formats_showcase/main.go", line 67, in main.readFile
*bruh.Err: reading file 'example.json'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "readme/formats_showcase/main.go", line 49, in main.configure
  File "readme/formats_showcase/main.go", line 59, in main.decodingData
*bruh.Err: decoding data

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "readme/formats_showcase/main.go", line 14, in main.main
  File "readme/formats_showcase/main.go", line 51, in main.configure
*bruh.Err: configuring application
```

<p align="right"><a href="#readme-top"><b>back to top ⇧</b></a></p>

#### Custom Formats

If you are not satisfied with the built-in formats you can easily create your own. Check the [json example](examples/custom_format/json.go) on how to accomplish that.

<p align="right"><a href="#readme-top"><b>back to top ⇧</b></a></p>

### Stack Depth

For optimization purposes the size of the collected stack frames per error and stack frames per chain during serialization is limited. If there are more, the result will be truncated. If you experience that your full stack frames are incomplete, you may raise the limit. There are two knobs that you can tweak:

**MaxErrorStackDepth**

`MaxErrorStackDepth` defines the maximum number of stack frames to capture per error. It influences the number of bytes allocated per error. If a function call stack exceeds this depth, the excess frames are truncated. This is generally not an issue, as the library merges stack traces across the error chain during serialization. To ensure full stack trace reconstruction, wrap errors from deeply nested calls to maintain stack frame overlap.

Its size must be defined at compilation time and thus it can only be set via build tag. Following build tags are available:

- `bruh.maxerrorstackdepth6`
- `bruh.maxerrorstackdepth12`
- `bruh.maxerrorstackdepth24` (default)
- `bruh.maxerrorstackdepth32`
- `bruh.maxerrorstackdepth48`

Build like this:

```sh
go build -tags bruh.maxerrorstackdepth32 ...
```

**MaxChainStackDepth**

Frequently Asked Questions

What is go-bruh?

go-bruh is a Error Handling library for the Go programming language. Error handling with stack traces, custom formatting, and observability integration

How do I install go-bruh?

Install go-bruh with the Go module system using `go get aisbergg/go-bruh`. Check the repository for the current installation instructions.

What category does go-bruh belong to?

go-bruh is listed under Error Handling, specifically Error Handling.

← Back to Error Handling