Golang errors with stacktrace and context

Overview

merry Build GoDoc Go Report Card

Add context to errors, including automatic stack capture, cause chains, HTTP status code, user messages, and arbitrary values.

The package is largely based on http://github.com/go-errors/errors, with additional inspiration from https://github.com/go-errgo/errgo and https://github.com/amattn/deeperror.

V2

github.com/ansel1/merry/v2 now replaces v1. v1 will continue to be supported. v1 has been re-implemented in terms of v2, and the two packages can be used together and interchangeably.

There are some small enhancements and changes to v1 with the introduction of v2:

  • err.Error() now always just prints out the basic error message. It no longer prints out details, user message, or cause. VerboseDefault() and SetVerboseDefault() no longer have any effect. To print more detailed error information, you must use fmt:

      // print err message and cause chain
      fmt.Printf("%v", err)    // %s works too
    
      // print details, same as Details(err)
      fmt.Printf("%v+", err) 
    
  • MaxStackDepth is no longer supported. Setting it has no effect. It has been replaced with GetMaxStackDepth() and SetMaxStackDepth(), which delegate to corresponding v2 functions.

  • New, Errorf, Wrap, and WrapSkipping now accept v2.Wrapper arguments, allowing a mixture of v1's fluent API style and v2's option-func API style.

  • Compatibility with other error wrapping libraries is improved. All functions which extract a value from an error will now search the entire chain of errors, even if errors created by other libraries are inserted in the middle of the chain, so long as those errors implement Unwrap().

Installation

go get github.com/ansel1/merry

Features

Merry errors work a lot like google's golang.org/x/net/context package. Merry errors wrap normal errors with a context of key/value pairs. Like contexts, merry errors are immutable: adding a key/value to an error always creates a new error which wraps the original.

merry comes with built-in support for adding information to errors:

  • stacktraces
  • overriding the error message
  • HTTP status codes
  • End user error messages

You can also add your own additional information.

The stack capturing feature can be turned off for better performance, though it's pretty fast. Benchmarks on an 2017 MacBook Pro, with go 1.10:

BenchmarkNew_withStackCapture-8      	 2000000	       749 ns/op
BenchmarkNew_withoutStackCapture-8   	20000000	        64.1 ns/op

Details

  • Support for go 2's errors.Is and errors.As functions

  • New errors have a stacktrace captured where they are created

  • Add a stacktrace to existing errors (captured where they are wrapped)

    err := lib.Read()
    return merry.Wrap(err)  // no-op if err is already merry
  • Add a stacktrace to a sentinel error

    var ParseError = merry.New("parse error")
    
    func Parse() error {
    	// ...
        return ParseError.Here() // captures a stacktrace here
    }
  • The golang idiom for testing errors against sentinel values or type checking them doesn't work with merry errors, since they are wrapped. Use Is() for sentinel value checks, or the new go 2 errors.As() function for testing error types.

    err := Parse()
    
    // sentinel value check
    if merry.Is(err, ParseError) {
       // ...
    }
    
    // type check
    if serr, ok := merry.Unwrap(err).(*SyntaxError); ok {
      // ...
    }
    
    // these only work in go1.13
    
    // sentinel value check
    if errors.Is(err, ParseError) {}
    
    // type check
    var serr *SyntaxError
    if errors.As(err, &serr) {}
  • Add to the message on an error.

    err := merry.Prepend(ParseError, "reading config").Append("bad input")
    fmt.Println(err.Error()) // reading config: parse error: bad input
  • Hierarchies of errors

    var ParseError = merry.New("Parse error")
    var InvalidCharSet = merry.WithMessage(ParseError, "Invalid char set")
    var InvalidSyntax = merry.WithMessage(ParseError, "Invalid syntax")
    
    func Parse(s string) error {
        // use chainable methods to add context
        return InvalidCharSet.Here().WithMessagef("Invalid char set: %s", "UTF-8")
        // or functions
        // return merry.WithMessagef(merry.Here(InvalidCharSet), "Invalid char set: %s", "UTF-8")
    }
    
    func Check() {
        err := Parse("fields")
        merry.Is(err, ParseError) // yup
        merry.Is(err, InvalidCharSet) // yup
        merry.Is(err, InvalidSyntax) // nope
    }
  • Add an HTTP status code

    merry.HTTPCode(errors.New("regular error")) // 500
    merry.HTTPCode(merry.New("merry error").WithHTTPCode(404)) // 404
  • Set an alternate error message for end users

    e := merry.New("crash").WithUserMessage("nothing to see here")
    merry.UserMessage(e)  // returns "nothing to see here"
  • Functions for printing error details

    err := merry.New("boom")
    m := merry.Stacktrace(err) // just the stacktrace
    m = merry.Details(err) // error message and stacktrace
    fmt.Sprintf("%+v", err) == merry.Details(err) // errors implement fmt.Formatter
  • Add your own context info

    err := merry.New("boom").WithValue("explosive", "black powder")

Basic Usage

The package contains functions for creating new errors with stacks, or adding a stack to error instances. Functions with add context (e.g. WithValue()) work on any error, and will automatically convert them to merry errors (with a stack) if necessary.

Capturing the stack can be globally disabled with SetStackCaptureEnabled(false)

Functions which get context values from errors also accept error, and will return default values if the error is not merry, or doesn't have that key attached.

All the functions which create or attach context return concrete instances of *Error. *Error implements methods to add context to the error (they mirror the functions and do the same thing). They allow for a chainable syntax for adding context.

Example:

package main

import (
    "github.com/ansel1/merry"
    "errors"
)

var InvalidInputs = errors.New("Input is invalid")

func main() {
    // create a new error, with a stacktrace attached
    err := merry.New("bad stuff happened")
    
    // create a new error with format string, like fmt.Errorf
    err = merry.Errorf("bad input: %v", os.Args)
    
    // capture a fresh stacktrace from this callsite
    err = merry.Here(InvalidInputs)
    
    // Make err merry if it wasn't already.  The stacktrace will be captured here if the
    // error didn't already have one.  Also useful to cast to *Error 
    err = merry.Wrap(err, 0)

    // override the original error's message
    err.WithMessagef("Input is invalid: %v", os.Args)
    
    // Use Is to compare errors against values, which is a common golang idiom
    merry.Is(err, InvalidInputs) // will be true
    
    // associated an http code
    err.WithHTTPCode(400)
    
    perr := parser.Parse("blah")
    err = Wrap(perr, 0)
    // Get the original error back
    merry.Unwrap(err) == perr  // will be true
    
    // Print the error to a string, with the stacktrace, if it has one
    s := merry.Details(err)
    
    // Just print the stacktrace (empty string if err is not a RichError)
    s := merry.Stacktrace(err)

    // Get the location of the error (the first line in the stacktrace)
    file, line := merry.Location(err)
    
    // Get an HTTP status code for an error.  Defaults to 500 for non-nil errors, and 200 if err is nil.
    code := merry.HTTPCode(err)
    
}

See inline docs for more details.

Plugs

License

This package is licensed under the MIT license, see LICENSE.MIT for details.

Comments
  • Add a way to use predefined merry errors to wrap

    Add a way to use predefined merry errors to wrap

    We generally define constants for often-used errors:

    var (
      ErrInvalidParameter = merry.
        New("Invalid parameter").
        WithUserMessage("Invalid parameter").
        WithHTTPCode(http.StatusBadRequest)
    )
    

    Then in the controller I'd love to be able to write something like:

    result, err := strconv.ParseBool(param)
    if err != nil {
      // this should save the current stack and the err object, but maybe override the http code
      // and user/error message if present.
      return ErrInvalidParameter.Wrap(err)
    }
    
    opened by cosmicz 7
  • Support callbacks to add context to a merry error when it is first captured

    Support callbacks to add context to a merry error when it is first captured

    When a merry error is first created, or first wrapped around an existing error, it would be cool to optionally invoke configurable a callback. This would allow users of merry to:

    1. capture a different kind of stacktrace (e.g. sentry stacktraces)
    2. attach other context information to the error.
    3. send the error to a log or error capture system

    Callbacks would be registered globally. They would be invoked whenever a new merry error is created, or when an error was wrapped with a merry error. They would be passed the current merry error, and a stackframe offset from where the merry error is being created, and would return a merry error.

    enhancement 
    opened by ansel1 7
  • gRPC codes

    gRPC codes

    Thanks for the awesome go package! Do you have plans to support gRPC codes? This is an extension to the HTTP codes.

    Ref: https://godoc.org/google.golang.org/grpc/codes

    opened by zhouzhuojie 4
  • merry.Errorf returns a stdlib error in some cases which breaks %+v

    merry.Errorf returns a stdlib error in some cases which breaks %+v

    Actual: fmt.Printf("%+v", merry.Errorf("%w", merry.New("boom"))) does not print a stacktrace Expected: It should print a stacktrace. Maybe the stacktrace of the wrapped error.

    This example reproduces the issue - https://go.dev/play/p/15d5C3TAbDh .

    As far as I understand the code, merry.Wrap called by merry.Errorf does not wrap the argument if it finds an existing stack trace in its wrap chain. Since merry.New("boom") is in the chain, merry.Errorf just returns the result of fmt.Errorf . However, in that case, fmt.Printf("%+v") will not use the Formatter interface implementation in merry.

    Workaround: add any value. merry.Errorf("%w", merry.New("boom"), merry.WithValue("foo", "bar")) works fine.

    opened by mnozhchev 3
  • Add a Cause property

    Add a Cause property

    Add a new native merry error property: "cause". This would be another error. Useful when a library emits errors of fixed types, and wants to wrap errors from lower level libraries in its own error types without losing information.

    Details() would be enhanced to print the stack of causes, perhaps labelling the innermost cause the "root cause".

    Cause(err) would get the next error down the cause stack. RootCause(err) would get the deepest cause.

    Could also consider enhancing Is(), or adding an alternate form, for comparing against any of the causes in the stack.

    enhancement 
    opened by ansel1 3
  • Should Location() return empty string?

    Should Location() return empty string?

    Hi,

    I feel like Location() should return an empty string if it's unknown where the error happened.

    • It's the null value of strings
    • A file with that name could actually exist (although unlikely)
    • Naming a file "" is probably impossible everywhere
    • Other functions like Stacktrace() return an empty string as well, not "no stacktrace" :)

    What do you think?

    opened by stroborobo 3
  • merry v1.6.0 fails go get

    merry v1.6.0 fails go get

    $ go get github.com/ansel1/merry
    go get: github.com/ansel1/[email protected] updating to
            github.com/ansel1/[email protected] requires
            github.com/ansel1/merry/[email protected]: invalid version: unknown revision 000000000000
    

    I also note that there is no v2 release tag yet (only beta). I'm guessing making a v2.0.0 release tag and updating the v1 go.mod to point to that release tag would fix it.

    I'm not sure if replace tags work as expected when a module is being imported as a library.

    opened by nvx 2
  • merry.Details() does not print attached values

    merry.Details() does not print attached values

    Calling merry.Details(err) does not print an error's attached values, i.e. merry.New("x").WithValue("lololol", "asdasdasd").

    Could it be made to print it? If it is just a matter of implementation, I would be happy to submit a pull request.

    opened by theiostream 2
  • Tests fail (nil value handling)

    Tests fail (nil value handling)

    In 99536c2 you changed the nil value handling and added a test for this (TestNilValues). It contains the following line:

    if Wrap(nil).WithHTTPCode(400).WithMessage("hey") { //...
    

    Maybe I misunderstand your commit, but you can't call methods on "true" nil interfaces, can you?

    http://play.golang.org/p/mmJoXJR9J1

    Did you mean

    if (WithMessage(WithHTTPCode(Wrap(nil), 400), "hey") != nil) { //...
    

    (This will still crash since WithValue doesn't handle the nil from the Wrap call it does.)

    opened by stroborobo 2
  • Fix

    Fix "TestSourceLine" cannot passed at windows os

    "TestSourceLine" cannot passed at windows. Bacause in windows, path format is "x://xx/xx", if use strings.Split method, then get 3parts, and strings package no RSplit method, so i use LastIndex method to get line number.

    opened by yuan1238y 1
  • Add flag with causes e.Error() to return the same as merry.Details(e)

    Add flag with causes e.Error() to return the same as merry.Details(e)

    Sometimes, errors are passed from merry-aware code to merry-unaware code. If it's the unaware code which is responsible for printing the error, it won't print the details. Might be helpful to have a flag on the merry error which changes the behavior of Error() to return the same as merry.Details(e).

    This came up in unit tests. I use an assertions library (testify) which checks for errors. If there's an error, it prints it. I end up having to wrap the assertion library in order to print out the full merry details of the error.

    enhancement 
    opened by ansel1 1
Releases(v2.1.0)
  • v2.1.0(Jan 9, 2023)

    • grpcstatus learns new AttachStatus() function, for attaching a status.Status to an error without losing the original error.
    • Now requires go1.16: lint and cover now make it quite difficult to support earlier versions
    Source code(tar.gz)
    Source code(zip)
  • v1.7.0(Jan 9, 2023)

  • v2.1.0-beta(Nov 30, 2022)

  • v2.0.2(Oct 10, 2022)

    • Errorf and Wrap/WrapSkipping now always return an error which implements fmt.Formatter, meaning fmt.Print("%v+", err) will always include the stacktrace (#26)
    Source code(tar.gz)
    Source code(zip)
  • v2.0.1(Feb 15, 2022)

  • v1.6.2(Feb 15, 2022)

  • v2.0.0(Feb 15, 2022)

    Official release of merry v2. Changes from last beta include some breaking changes:

    • Dropped support for older versions of go. 1.12 or greater is now required.
    • Dropped the goerrors package. This was an optional hook which adapted the stacktrace captured by go-errors to merry stacktraces. This adaptation didn't require importing go-errors, so this capability is now built in natively to merry v2, no hook required.
    • Some functions were removed from the grpcstatus package, to reduce the scope and complexity of the package. This package now focuses solely on encoding enriched merry errors into enriched grpc Statuses.
    Source code(tar.gz)
    Source code(zip)
  • v2.0.0-beta.12(Nov 21, 2021)

  • v2.0.0-beta.11(Nov 21, 2021)

  • v1.6.1(May 31, 2021)

    Trying to fix go get on merry v1. 1.6.0 introduced a dependency on merry/v2, which doesn't have a non-beta release tag yet. go get was not finding the v2 beta tags. I think explicitly listing one of the v2 beta tags in v1's go.mod should work.

    Source code(tar.gz)
    Source code(zip)
  • v2.0.0-beta.10(May 28, 2021)

  • v1.6.0(May 28, 2021)

  • v2.0.0-beta.9(May 25, 2021)

    • grpcstatus errors should implement fmt.Formatter
    • merry/v2 learned Format(), a convenience function for implementing fmt.Formatter on custom error impls
    Source code(tar.gz)
    Source code(zip)
  • v2.0.0-beta.8(Mar 9, 2021)

  • v2.0.0-beta.7(Mar 7, 2021)

    • Removed Sentinel and Sentinelf, replaced with more flexible Apply and ApplySkipping. Similar to Sentinel, it wrap errors without running hooks or capturing a stack.
    • Added once-only hooks, which are only applied to a particular error once, regardless how many times it is wrapped.
    Source code(tar.gz)
    Source code(zip)
  • v2.0.0-beta.6(Mar 2, 2021)

    • Added Lookup(), like Value, but also returns a boolean indicating whether the value was found.

    • When unwrapping errors that have causes, the order of errors returned will be more intuitive: all the errors in the chain will be returned first, all the way to the root of that chain. When the current chain is exhausted, then the cause will be returned. Only the latest cause attached to the error will be returned, ignoring earlier causes. For example:

      err := New("boom") err = Wrap(err, WithMessage("bang")) err = Wrap(err, WithCause("ioerror")) err = Wrap(err, WithCause("httperror")) // overrides ioerror cause err = Wrap(err, WithMessage("crash"))

    Iterating through this chain of errors will yield "crash", "bang", "boom", "httperror", nil.

    Source code(tar.gz)
    Source code(zip)
  • v2.0.0-beta.5(Mar 1, 2021)

    • Learned Sentinel and Sentinelf, to create sentinel errors with no stack trace attached initially.
    • grpcstatus package no longer automatically derives status details from error attributes.
    Source code(tar.gz)
    Source code(zip)
  • v2.0.0-beta.4(Feb 26, 2021)

  • v2.0.0-beta.3(Feb 26, 2021)

    • Renamed the Prepend/Append wrappers to PrependMessage/AppendMessage
    • Re-added the Prepend/Append package functions to make migration from v1 simpler
    Source code(tar.gz)
    Source code(zip)
  • v2.0.0-beta.2(Feb 25, 2021)

  • v2.0.0-beta.1(Feb 25, 2021)

    v2 is a rewrite of merry, using a simpler API based on the functional options pattern, rather than the fluent API pattern.

    v1 has been rewritten in terms of v2, and the two version can be mixed freely. errors created by one are completely compatible with the other. v1's New, Errorf, Wrap, and WrapSkipping functions accept v2's functional options, to ease migration.

    v2 also offers some improvements over v1:

    • No Error interface. v2 is written purely in terms of the builtin error interface
    • More granular control over stack capture: override the global setting for particular errors, either forcing a capture, or suppressing it
    • Hooks: global functions which can intercept and process all errors passing through the merry package
    • Custom stack traces: using hooks and wrappers, you can attach external stacktraces to errors, or replace merry's built in capturing logic
    • grpcstatus package: a package which adds support for translating between merry errors and grpc Status objects
    Source code(tar.gz)
    Source code(zip)
  • v1.5.1(Jan 31, 2020)

  • v1.5.0(Jul 1, 2019)

    Merry errors now work with go1.13's new errors.Is(), errors.As(), and errors.Unwrap() functions. Is() and As() will match a merry error's wrapped errors, the root error, or the error's cause. errors.Unwrap(err) will return the merry error's cause.

    Source code(tar.gz)
    Source code(zip)
  • v1.4.0(Mar 4, 2019)

    Added module support, in prep for v2. module support let me simplify the makefile and travis build steps a bit as well.

    Consumers of merry which are pre-1.11, or are not using module yet should still be able to consume merry. It has no non-test dependencies anyway.

    Source code(tar.gz)
    Source code(zip)
  • v1.3.2(Mar 4, 2019)

    Fix for printing incorrect stacktraces. Replaced runtime.FuncForPC() with runtime.CallerFrames(), which handles inlined and compiler-generated wrapper functions. I should have done this a while ago, but didn't realize there was a problem until go 1.12. The compiler now inlines more aggressively, which finally broke the tests.

    Source code(tar.gz)
    Source code(zip)
  • v1.3.1(Sep 14, 2018)

    Fixes a bug with using Prepend/Append with errors that have causes. The bug was causing the Cause's error message to appear twice in the final error's message, i.e.:

    merry.New("red").WithCause(merry.New("green")).Prepend("blue").Error() // blue: red: green: green
    

    As a result, the Message() function will no longer include the cause's message. It will only return the top level error's message. For example, in 1.3.0:

    e := merry.New("red").WithCause(merry.New("green"))
    Message(e) // red: green
    e.Error() // red: green, same as Message(e)
    

    Now in 1.3.1:

    e := merry.New("red").WithCause(merry.New("green"))
    Message(e) // red
    e.Error() // red: green
    
    Source code(tar.gz)
    Source code(zip)
  • v1.3.0(Aug 24, 2018)

    This release addresses #17: packages which add custom properties to errors can now register those properties to be included in the output of Details(). See the new RegisterDetail function.

    Source code(tar.gz)
    Source code(zip)
  • v1.2.1(Aug 24, 2018)

    Fixed return type of WithCause(). Was error, should have been merry.Error. This is technically a breaking change, but the bug has been in the wild more than a couple hours.

    Source code(tar.gz)
    Source code(zip)
  • v1.2.0(Aug 24, 2018)

    Causes! merry now supports adding a cause to an error. The causes' messages are appended to the top error's message, e.g. "reading failed: connection error: EOF". Details() now includes the details of the entire causal chain. Is() will test now test the error and the error's causes against the arguments. Cause() and RootCause() return the direct cause and the innermost cause of an error. Fixes #21 and #13 .

    Also updated the docs.

    Source code(tar.gz)
    Source code(zip)
  • v1.1.0(Jul 20, 2018)

Owner
Russ Egan
Russ Egan
Common juju errors and functions to annotate errors. Based on juju/errgo

errors import "github.com/juju/errors" The juju/errors provides an easy way to annotate errors without losing the original error context. The exporte

Juju 1.3k Dec 30, 2022
Linter for errors.Is and errors.As

erris erris is a program for checking that errors are compared or type asserted using go1.13 errors.Is and errors.As functions. Install go get -u gith

Roman Budnikov 48 Nov 27, 2022
This structured Error package wraps errors with context and other info

RErr package This structured Error package wraps errors with context and other info. It can be used to enrich logging, for example with a structured l

Rohan Allison 0 Jan 21, 2022
An errors package optimized for the pkg/errors package

errors An errors package optimized for the pkg/errors package Use Download and install go get github.com/dobyte/errors API // New Wrapping for errors.

Fuxiao 3 Mar 2, 2022
Golang errors with stack trace and source fragments.

Golang Errors with Stack Trace and Source Fragments Tired of uninformative error output? Probably this will be more convenient: Example package main

null 753 Dec 17, 2022
A Go (golang) package for representing a list of errors as a single error.

go-multierror go-multierror is a package for Go that provides a mechanism for representing a list of error values as a single error. This allows a fun

HashiCorp 1.8k Jan 1, 2023
Hierarchical errors reporting done right in Golang

Hierarchical errors made right Hate seeing error: exit status 128 in the output of programs without actual explanation what is going wrong? Or, maybe,

reconquest 74 Nov 9, 2021
Package semerr helps to work with errors in Golang.

semerr Package semerr helps to work with errors in Golang. Const error An error that can be defined as const. var errMutable error = errors.New("mutab

Maxim Krivchun 5 Oct 30, 2022
A Nostress Errors Package For Golang

A Nostress Errors Package For Golang

null 0 Nov 2, 2021
eris provides a better way to handle, trace, and log errors in Go 🎆

eris Package eris provides a better way to handle, trace, and log errors in Go. go get github.com/rotisserie/eris Why you'll want to switch to eris Us

null 1.2k Dec 29, 2022
Go tool to wrap and fix errors with the new %w verb directive

errwrap Wrap and fix Go errors with the new %w verb directive. This tool analyzes fmt.Errorf() calls and reports calls that contain a verb directive t

Fatih Arslan 354 Nov 10, 2022
The Emperor takes care of all errors personally

The Emperor takes care of all errors personally. Go's philosophy encourages to gracefully handle errors whenever possible, but some times recovering f

Emperror 281 Jan 9, 2023
A drop-in replacement for Go errors, with some added sugar! Unwrap user-friendly messages, HTTP status code, easy wrapping with multiple error types.

Errors Errors package is a drop-in replacement of the built-in Go errors package with no external dependencies. It lets you create errors of 11 differ

Kamaleshwar 47 Dec 6, 2022
harvest Go errors with ease

Pears Harvest Go Errors with Ease Introduction Pears helps reduce the boilerplate and ensure correctness for common error-handling scenarios: Panic re

Billy Peake 7 Apr 25, 2021
SupErr -- Go stdlib errors with super powers

superr SupErr -- Go stdlib errors with super powers. Pronounced super with a French accent :D Build a stack of errors compatible with Go stdlib and er

Golang libraries for everyone 26 Nov 15, 2022
Simple Go library for typed errors creation

go-typed-errors Why this repo was created? Main reason was to create additional methods for better error typing support. Why not to use errors.As and

Bogdan 2 Nov 5, 2021
Simple Go library for typed const errors creation

go-errors Why this repo was created? Main reason was to create additional methods for better error typing support. Features Errors as constants errors

Bogdan 2 Nov 5, 2021
This project is for parsing Artifactory logs for errors

hello-frog About this plugin This plugin is a template and a functioning example for a basic JFrog CLI plugin. This README shows the expected structur

Tatarao 0 Nov 30, 2021
Go package for errors with chained stack traces

errstack: Go errors with chained stack traces errstack is a Go package for creating errors with stack traces. It is heavily inspired by github.com/pkg

Greg Ward 0 Nov 27, 2021