A Go linter to check that errors from external packages are wrapped

Overview

Wrapcheck

Go Report Card

A simple Go linter to check that errors from external packages are wrapped during return to help identify the error source during debugging.

More detail in this article

Install

Go >= v1.16

$ go install github.com/tomarrell/wrapcheck/v2/cmd/[email protected]

Wrapcheck is also available as part of the golangci-lint meta linter. Docs and usage instructions are available here. When used with golangci-lint, configuration is integrated with the .golangci.yaml file.

Configuration

You can configure wrapcheck by using a .wrapcheck.yaml file in either the local directory, or in your home directory.

# An array of strings which specify substrings of signatures to ignore. If this
# set, it will override the default set of ignored signatures. You can find the
# default set at the top of ./wrapcheck/wrapcheck.go.
ignoreSigs:
- .Errorf(
- errors.New(
- errors.Unwrap(
- .Wrap(
- .Wrapf(
- .WithMessage(

Usage

To lint all the packages in a program:

$ wrapcheck ./...

Testing

This linter is tested using analysistest, you can view all the test cases under the testdata directory.

TLDR

If you've ever been debugging your Go program, and you've seen an error like this pop up in your logs.

time="2020-08-04T11:36:27+02:00" level=error error="sql: error no rows"

Then you know exactly how painful it can be to hunt down the cause when you have many methods which looks just like the following:

func (db *DB) getUserByID(userID string) (User, error) {
	sql := `SELECT * FROM user WHERE id = $1;`

	var u User
	if err := db.conn.Get(&u, sql, userID); err != nil {
		return User{}, err // wrapcheck error: error returned from external package is unwrapped
	}

	return u, nil
}

func (db *DB) getItemByID(itemID string) (Item, error) {
	sql := `SELECT * FROM item WHERE id = $1;`

	var i Item
	if err := db.conn.Get(&i, sql, itemID); err != nil {
		return Item{}, err // wrapcheck error: error returned from external package is unwrapped
	}

	return i, nil
}

The problem here is that multiple method calls into the sql package can return the same error. Therefore, it helps to establish a trace point at the point where error handing across package boundaries occurs.

To resolve this, simply wrap the error returned by the db.Conn.Get() call.

func (db *DB) getUserByID(userID string) (User, error) {
	sql := `SELECT * FROM user WHERE id = $1;`

	var u User
	if err := db.Conn.Get(&u, sql, userID); err != nil {
		return User{}, fmt.Errorf("failed to get user by ID: %v", err) // No error!
	}

	return u, nil
}

func (db *DB) getItemByID(itemID string) (Item, error) {
	sql := `SELECT * FROM item WHERE id = $1;`

	var i Item
	if err := db.Conn.Get(&i, sql, itemID); err != nil {
		return Item{}, fmt.Errorf("failed to get item by ID: %v", err) // No error!
	}

	return i, nil
}

Now, your logs will be more descriptive, and allow you to easily locate the source of your errors.

time="2020-08-04T11:36:27+02:00" level=error error="failed to get user by ID: sql: error no rows"

A further step would be to enforce adding stack traces to your errors instead using errors.WithStack() however, enforcing this is out of scope for this linter for now.

Why?

Errors in Go are simple values. They contain no more information about than the minimum to satisfy the interface:

type Error interface {
  Error() string
}

This is a fantastic feature, but can also be a limitation. Specifically when you are attempting to identify the source of an error in your program.

As of Go 1.13, error wrapping using fmt.Errorf(...) is the recommend way to compose errors in Go in order to add additional information.

Errors generated by your own code are usually predictable. However, when you have a few frequently used libraries (think sqlx for example), you may run into the dilemma of identifying exactly where in your program these errors are caused.

In other words, you want a call stack.

This is especially apparent if you are a diligent Gopher and always hand your errors back up the call stack, logging at the top level.

So how can we solve this?

Solution

Wrapping errors at the call site.

When we call into external libraries which may return an error, we can wrap the error to add additional information about the call site.

e.g.

...

func (db *DB) createUser(name, email, city string) error {
  sql := `INSERT INTO customer (name, email, city) VALUES ($1, $2, $3);`

  if _, err := tx.Exec(sql, name, email, city); err != nil {
    // %v verb preferred to prevent error becoming part of external API
    return fmt.Errorf("failed to insert user: %v", err)
  }

  return nil
}

...

This solution allows you to add context which will be handed to the caller, making identifying the source easier during debugging.

Contributing

As with most static analysis tools, this linter will likely miss some obscure cases. If you come across a case which you think should be covered and isn't, please file an issue including a minimum reproducible example of the case.

License

This project is licensed under the MIT license. See the LICENSE file for more details.

Comments
  • Configuration to define what is external

    Configuration to define what is external

    Thanks for the nice linter!

    I was curious if you considered cases where the meaning of external could mean something other than outside the current package. In my case I would like to find cases where errors from outside the current module or -local string are not wrapped. I see the levels being package, module, organization. If this interests you I'd like to work on it with you otherwise I could simply fork the project

    enhancement 
    opened by gganley 16
  • Proposal: ignore returning value of specify package's function

    Proposal: ignore returning value of specify package's function

    Hello 👋 Thank you for developing such a great tool!

    I'm using a library that wraps and manages errors like below.

    https://github.com/morikuni/failure https://github.com/pkg/errors

    Currently, wrapcheck also reports for these librarie's function.

    example

    error returned from external package is unwrapped (wrapcheck)
                    return nil, errors.Wrap(err, "failed to do something")
                                      ^
    

    This is, of course, the correct behavior. But I don't need these report from wrapcheck. So, I ignore these reports with golangci-lint's exclude-rules like below.

    issues:
      exclude-rules:
       - source: "errors"
          linters:
            - wrapcheck
        - source: "failure"
          linters:
            - wrapcheck
    

    https://golangci-lint.run/usage/configuration/

    It would be very helpful to be able to configure these settings on the wrapcheck side.

    (This proposal may be similar to #2)

    Thanks

    bug enhancement 
    opened by sanposhiho 13
  • Consider whitelisting cases where function has single error return case

    Consider whitelisting cases where function has single error return case

    With the following code:

    https://github.com/flexkube/libflexkube/blob/9e0182c5c9e97e748e5945d122d686aebd4b4e6e/cli/flexkube/resource.go#L352-L374

    I think the linter should allow to skip error wrapping, as the caller should do the wrapping. If error occurs, it is known that the error comes from this line. What do you think? Or maybe this should be somehow configurable?

    enhancement 
    opened by invidian 13
  • dev: refactor `regexp` usage

    dev: refactor `regexp` usage

    This PR intends to bring the next features/fixes to wrapcheck:

    1. avoid compiling regexp rules into regexp.Regexp for checking each error returning function.
    2. removing os.Exit(1) in case fail to compile regexp rule from the codebase ( we can't handle such cases in golangci-lint: output is hidden, so no log messages received, which ends in no issues or errors comes from wrapcheck if bad regexp provided).

    P.S. The rest of the coding is changed by gofumpt automatically. P.P.S. It would be also great to release fix changes asap (so we update golangci-lint).

    opened by butuzov 9
  • Great linter! Just one question about %w vs %v

    Great linter! Just one question about %w vs %v

    Hi,

    Forgive my question but I think there is a reason you are using %v rather than %w on errorf to wrap errors but I am not sure I understand it.

    I have always been using %w with returning to new errors that wrap previous ones but I notice here you are using %v.

    What is the reason ?

    Thanks again!

    opened by iangregsondev 7
  • wrapcheck on goerr113 wrapping %w

    wrapcheck on goerr113 wrapping %w

    Wrapcheck on goerr113 wrapping %w

    I understand that Wrapcheck linter is all about wrapping the error coming from external packages but I think the error coming from the external package should never be used as %w reason being that this forces DevUsers to import the package A where the error is being returned as well as the internal package B where the error is being thrown and possible where the Sentinel lives if we ever need to check on the error type with Error.Is and Error.As creating a form of coupling to the internal detail implementation of package A

    import "pkg/Process"
    
    var ErrProccesFailed = "process failed due"
    {...}
    err := Process()
    return fmt.Errorf("%w : %v", ErrProccesFailed,err)
    

    I think Sentinel errors need to be created to wrap the err coming from the external package so only one import is needed to check on the Error.Is or Error.As meanwhile, we make sure the external package error is wrapped into a more meaningful error at this package level without exposing any type of possible coupling

    opened by K4L1Ma 5
  • New release?

    New release?

    I'd like to use the new ignorePackageGlobs feature

    https://github.com/tomarrell/wrapcheck/commit/d409df3395169cccdb7f39aea3092e20c5e69a9c

    But it wasn't released yet.

    Do you plan to release it?

    opened by r2k1 5
  • Support for errors.Wrap(f)

    Support for errors.Wrap(f)

    Hello,

    In our project we use golangci-lint and have a lot of similar code:

    func foo() (object, error)
      err := doSomething()
      if err != nil {
        return nil, errors.Wrapf(err, "action failed for %v", anyAdditionalInfo)
      }
    }
    

    Starting from version golangci-lint 1.39.0 built from 9aea4aee on 2021-03-26T08:02:53Z all those lines cannot pass wrapcheck:

    foo/bar:42:16: error returned from external package is unwrapped (wrapcheck)
                             return nil, errors.Wrapf(err, "action failed for %v", anyAdditionalInfo)
    

    Is that a false positive error ? Or am I missing something ?

    Thanks.

    opened by joy4eg 5
  • false positive messages on legal errors from the same file

    false positive messages on legal errors from the same file

    file internal/modifier/modifiers_aggregate.go contains code:

    var (
        ErrPlaylistsNotTwo = errors.New("playlists not two")
    )
    
    if len(playlists) != 2 {
        return nil, ErrPlaylistsNotTwo
    }
    

    causes the following: internal/modifier/modifiers_aggregate.go:197:15: error returned from external package is unwrapped (wrapcheck) return nil, ErrPlaylistsNotTwo

    enhancement 
    opened by ahlininv 4
  • Linter triggers on wrapper functions

    Linter triggers on wrapper functions

    Considering following code:

    // Deploy checks current status of deployed group of instances and updates them if there is some
    // configuration drift.
    func (a *apiLoadBalancers) Deploy() error {
      return a.containers.Deploy()
    }
    

    Right now linter triggers on it, but it seems very counter-intuitive.

    Used via golangci-lint version v1.39.0

    opened by invidian 3
  • Was v1.0.0 republished?

    Was v1.0.0 republished?

    It looks like the v1.0.0 tag may have been published multiple times with different commits, leading to module checksum errors:

    go: downloading honnef.co/go/tools v0.1.3
    verifying github.com/tomarrell/[email protected]: checksum mismatch
    	downloaded: h1:Vlt2WgQOtsuhOBvJsqnT79c0BmN568PxEcB+EMNm/yY=
    	go.sum:   h1:e/6yv/rH08TZFvkYpaAMrgGbaQHVFdzaPPv4a5EIu+o=
    
    SECURITY ERROR
    This download does NOT match an earlier download recorded in go.sum.
    The bits may have been replaced on the origin server, or an attacker may
    have intercepted the download attempt.
    
    For more information, see 'go help module-auth'.
    

    pkg.go.dev indicates that v1.0.0 was published on 3/17/2021, but this repo indicates it was published yesterday, 3/29/2021.

    opened by Aneurysm9 3
  • ignorePackageGlobs doesn't appear to be working

    ignorePackageGlobs doesn't appear to be working

    Hey, great tool! This has been really useful to track down places I can apply some new error context packages I've been working on to provide additional structured information to errors.

    After applying error contexts across our project I noticed a few places where it was unnecessary so I decided to disable reporting of imports from local packages (/pkg, etc)

    However, maybe I'm misunderstanding this config option, but it doesn't appear to do what I expected.

    Here's the configuration file: https://github.com/Southclaws/storyden/blob/main/.golangci.yml#L28

    And here are the offending lines:

    • https://github.com/Southclaws/storyden/blob/main/pkg/transports/http/bindings/threads.go#L39
    • https://github.com/Southclaws/storyden/blob/main/pkg/transports/http/bindings/threads.go#L52

    I assumed that a ignorePackageGlobs value of github.com/Southclaws/storyden/* would ignore these two lines as they are functions that are imported from within this pattern:

    • i.thread_svc.Create: github.com/Southclaws/storyden/pkg/services/thread.Service.Create
    • i.thread_svc.ListAll: github.com/Southclaws/storyden/pkg/services/thread.Service.Create

    The actual output from running golangci-lint run:

    storyden on  main [!] via 🐹 v1.18.3 on ☁️  (eu-central-1) 
    ❯ golangci-lint run
    
    pkg/transports/http/bindings/threads.go:39:10: error returned from interface method should be wrapped: sig: func (github.com/Southclaws/storyden/pkg/services/thread.Service).Create(ctx context.Context, title string, body string, authorID github.com/Southclaws/storyden/pkg/resources/account.AccountID, categoryID github.com/Southclaws/storyden/pkg/resources/category.CategoryID, tags []string) (*github.com/Southclaws/storyden/pkg/resources/thread.Thread, error) (wrapcheck)
                    return err
                           ^
    pkg/transports/http/bindings/threads.go:52:10: error returned from interface method should be wrapped: sig: func (github.com/Southclaws/storyden/pkg/services/thread.Service).ListAll(ctx context.Context, before time.Time, max int) ([]*github.com/Southclaws/storyden/pkg/resources/thread.Thread, error) (wrapcheck)
                    return err
                           ^
    

    I wondered if it was golangci lint just using an outdated version but this occurs on the latest standalone version of wrapcheck too.

    I tried with a .wrapcheck.yaml file with:

    ignorePackageGlobs:
      - github.com/Southclaws/storyden/*
      - github.com/Southclaws/storyden/pkg/*
      - pkg/*
      - ./pkg/*
    

    but I can't seem to get any patterns to ignore correctly.

    Thanks!

    opened by Southclaws 1
  • Support Gorm-style error checking

    Support Gorm-style error checking

    Gorm uses non-standard error format to support it's chainable API. It would be nice if there was a way to configure wrapcheck to match a *.\.Error regex

    tx := tx.Find(&customer)
    if tx.Error != nil {
        return nil, tx.Error // wrapcheck should warn here
    }
    
    
    abc := tx.Find(&customer)
    if abc.Error != nil {
        return nil, abc.Error // wrapcheck should warn here
    }
    
    opened by cdignam-segment 1
  • Embedded interfaces are hard to ignore

    Embedded interfaces are hard to ignore

    When an interface is embedded, the reported interface name isn't what you need to ignore. Concretely, extend wrapcheck/testdata/config_ignoreInterfaceRegexps/main.go with the following, and run the test.

    type embedder interface {
    	errorer
    }
    
    func embed(fn embedder) error {
    	var str string
    	return fn.Decode(&str) // errorer interface ignored as per `ignoreInterfaceRegexps`
    }
    

    Unexpectedly, you'll see the following:

    --- FAIL: TestAnalyzer (5.40s) --- FAIL: TestAnalyzer/config_ignoreInterfaceRegexps (0.25s) analysistest.go:452: /home/murman/src/wrapcheck/wrapcheck/testdata/config_ignoreInterfaceRegexps/main.go:33:9: unexpected diagnostic: error returned from interface method should be wrapped: sig: func (_/home/murman/src/wrapcheck/wrapcheck/testdata/config_ignoreInterfaceRegexps.errorer).Decode(v interface{}) error

    Note that the ignores include errorer and the reported interface is config_ignoreInterfaceRegexps.errorer. However to suppress this you actually need to suppress embedder.

    I believe this comes down to the difference between name and fnSig in reportUnwrapped, which come from sel.Sel and sel.X respectively, but the path forward is unclear to me. https://github.com/tomarrell/wrapcheck/blob/213318509af6a003be2be752da826d269149ba4d/wrapcheck/wrapcheck.go#L257-L260

    opened by MichaelUrman 0
  • Flag to ignore main module

    Flag to ignore main module

    As has been requested, ignoring the main module in a project via a flag to prevent having to configure the package ignore for each project.

    Something along the lines of ignore: go list -m.

    enhancement 
    opened by tomarrell 0
  • False-positive for anonymous functions

    False-positive for anonymous functions

    Wrapcheck throws an error for the next case:

    import "errors"
    
    func Test_AnonFunc(t *testing.T) {
    	test := func() error {
    		return errors.New("test")
    	}
    	fmt.Println(test())
    }
    

    error returned from external package is unwrapped: sig: func errors.New(text string) error

    bug 
    opened by r2k1 1
  • Named return values not reported

    Named return values not reported

    Try this out

    package main
    
    import (
    	"errors"
    )
    
    func somethingDangerous() error {
    	return errors.New("fake err")
    }
    
    func run() (err error) {
    	err = somethingDangerous()
    	return
    }
    
    func main() {
    	if err := run(); err != nil {
    		println(err)
    	}
    }
    

    You'll see that wrap check does not detect that err is not being wrapped.

    bug 
    opened by cornfeedhobo 3
Releases(v2.6.2)
  • v2.6.2(Jun 14, 2022)

  • v2.6.1(Apr 29, 2022)

  • v2.6.0(Mar 24, 2022)

    This release adds the config parameter ignoreInterfaceRegexps, which provides the ability to specify regex patterns which will ignore calls through an interface with a matching name.

    For example:

    ignoreInterfaceRegexps:
    - [dD]ecoder
    

    The above configuration will cause errors returned by methods called on an interface named decoder or Decoder to skip wrapcheck analysis.

    Many thanks to @guillaumeio for the contribution!

    Source code(tar.gz)
    Source code(zip)
  • v2.5.0(Feb 17, 2022)

    This releases front-loads the compilation of regexps provided via the config file, enabling immediate error reporting.

    It also removes the os.Exit(1) upon failing to compile the regexps, to play nicer with golangci-lint.

    Source code(tar.gz)
    Source code(zip)
  • v2.4.0(Oct 29, 2021)

    This release adds the ability to configure wrapcheck to ignore signatures using regex matching using the ignoreSigRegexps config value. Example configuration is below.

    # An array of strings which specify regular expressions of signatures to ignore.
    # This is similar to the ignoreSigs configuration above, but gives slightly more
    # flexibility.
    ignoreSigRegexps:
    - \.New.*Error\(
    
    Source code(tar.gz)
    Source code(zip)
  • v2.3.1(Oct 1, 2021)

  • v2.3.0(Jul 29, 2021)

    This release adds the ability to configure glob patterns to ignore unwrapped errors returned by functions contained in packages that match the glob pattern.

    Below is an example configuration.

    # An array of glob patterns which, if any match the package of the function
    # returning the error, will skip wrapcheck analysis for this error. This is
    # useful for broadly ignoring packages and/or subpackages from wrapcheck
    # analysis. There are no defaults for this value.
    ignorePackageGlobs:
    - encoding/*
    - github.com/pkg/*
    
    Source code(tar.gz)
    Source code(zip)
  • v2.2.0(Jun 15, 2021)

    This release adds better support for github.com/pkg/errors by default. Including ignore cases for the signatures:

    • .WithMessagef(
    • .WithStack(
    Source code(tar.gz)
    Source code(zip)
  • v2.0.0(Apr 30, 2021)

  • v1.2.0(Apr 19, 2021)

  • v1.1.0(Mar 31, 2021)

  • v1.0.0(Mar 29, 2021)

    This release takes wrapcheck to v1. It's currently stable and breaking changes will result in major version bumps.

    An upcoming minor version release will allow for configurable ignored signatures in order to have more control over where errors are reported.

    Source code(tar.gz)
    Source code(zip)
Owner
Tom Arrell
Senior Backend Engineer @sumup — Lover of Rust and Go, hack around building keyboards when I'm bored
Tom Arrell
The most opinionated Go source code linter for code audit.

go-critic Highly extensible Go source code linter providing checks currently missing from other linters. There is never too much static code analysis.

null 1.4k Sep 22, 2022
[mirror] This is a linter for Go source code.

Golint is a linter for Go source code. Installation Golint requires a supported release of Go. go get -u golang.org/x/lint/golint To find out where g

Go 4k Sep 23, 2022
Staticcheck - The advanced Go linter

The advanced Go linter Staticcheck is a state of the art linter for the Go programming language. Using static analysis, it finds bugs and performance

Dominik Honnef 5k Sep 26, 2022
A linter that handles struct tags.

Tagliatelle A linter that handles struct tags. Supported string casing: camel pascal kebab snake goCamel Respects Go's common initialisms (e.g. HttpRe

Ludovic Fernandez 18 Aug 15, 2022
a simple golang SSA viewer tool use for code analysis or make a linter

ssaviewer A simple golang SSA viewer tool use for code analysis or make a linter ssa.html generate code modify from src/cmd/compile/internal/ssa/html.

null 7 May 17, 2022
The Golang linter that checks that there is no simultaneous return of `nil` error and an invalid value.

nilnil Checks that there is no simultaneous return of nil error and an invalid value. Installation & usage $ go install github.com/Antonboom/[email protected]

Anton Telyshev 12 Aug 30, 2022
Go linter which checks for dangerous unicode character sequences

bidichk - checks for dangerous unicode character sequences bidichk finds dangerous unicode character sequences in Go source files. Considered dangerou

Lucas Bremgartner 24 Jul 5, 2022
Go linter that checks types that are json encoded - reports unsupported types and unnecessary error checks

Checks types passed to the json encoding functions. Reports unsupported types and reports occations, where the check for the returned error can be omited.

Lucas Bremgartner 26 Jun 17, 2022
Linter for PostgreSQL

Использование Проверить миграции: oh-my-pg-linter check ./migrations/*.sql Добавить директории с дополнительными проверками (переопределение - кто пос

Denis Kayumov 0 Nov 25, 2021
containedctx detects is a linter that detects struct contained context.Context field

containedctx containedctx detects is a linter that detects struct contained context.Context field Instruction go install github.com/sivchari/contained

sivchari 9 Jun 19, 2022
World's spookiest linter

nosleep The world's spookiest linter nosleep is a golang-ci compatible linter which checks for and fails if it detects usages of time.Sleep. Why did y

Elliot Williams 0 Jan 17, 2022
Go linter to analyze expression groups: require 'import' declaration groups

grouper — a Go linter to analyze expression groups Installation

null 0 Jun 19, 2022
funcresult — a Go linter to analyze function result parameters

Go linter to analyze function result parameters: require named / unnamed function result parameters

null 0 Jan 27, 2022
nostdglobals is a simple Go linter that checks for usages of global variables defined in the go standard library

nostdglobals is a simple Go linter that checks for usages of global variables defined in the go standard library

Nassos Kat 1 Feb 17, 2022
Goalinter-v1: Goa framework (version1) linter

goavl: Goa framework (ver1) linter goavlは、goa version1(フォーク版)のlinterです。開発目的は、goa

CHIKAMATSU Naohiro 1 Jul 28, 2022
Linter for Go's fmt.Errorf message

wrapmsg wrapmsg is Go code linter. this enforces fmt.Errorf's message when you wrap error. Example // OK ???? if err := pkg.Cause(); err != nil { re

Shinnosuke Sawada 9 Jul 4, 2022
misspelled word linter for Go comments, string literals and embedded files

gospel The gospel program lints Go source files for misspellings in comments, strings and embedded files. It uses hunspell to identify misspellings an

Dan Kortschak 30 Aug 6, 2022
Clean architecture validator for go, like a The Dependency Rule and interaction between packages in your Go projects.

Clean Architecture checker for Golang go-cleanarch was created to keep Clean Architecture rules, like a The Dependency Rule and interaction between mo

Robert Laszczak 620 Sep 22, 2022
Find outdated golang packages

This project is not supported anymore Go-outdated is minimalistic library that helps to find outdated packages hosted on github.com in your golang pro

null 43 Sep 8, 2022