{"id":24578017,"url":"https://github.com/colinc86/wrappederror","last_synced_at":"2025-06-10T17:04:27.798Z","repository":{"id":57568849,"uuid":"343567275","full_name":"colinc86/wrappederror","owner":"colinc86","description":"An over-engineered error type for Go.","archived":false,"fork":false,"pushed_at":"2021-03-12T06:07:52.000Z","size":498,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-23T23:56:59.986Z","etag":null,"topics":["error","go","golang","interface","unwrap","wrap","wrapped"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/colinc86.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-03-01T21:51:54.000Z","updated_at":"2021-03-12T06:07:54.000Z","dependencies_parsed_at":"2022-08-30T05:50:25.045Z","dependency_job_id":null,"html_url":"https://github.com/colinc86/wrappederror","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/colinc86%2Fwrappederror","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/colinc86%2Fwrappederror/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/colinc86%2Fwrappederror/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/colinc86%2Fwrappederror/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/colinc86","download_url":"https://codeload.github.com/colinc86/wrappederror/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244047663,"owners_count":20389207,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["error","go","golang","interface","unwrap","wrap","wrapped"],"created_at":"2025-01-23T23:57:09.576Z","updated_at":"2025-03-17T14:23:49.547Z","avatar_url":"https://github.com/colinc86.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg width=\"300\" height=\"300\" src=\"assets/wrappederror.png\"\u003e\n\u003c/p\u003e\n\n# Package wrappederror\n\n[![Go Tests](https://github.com/colinc86/wrappederror/actions/workflows/go-test.yml/badge.svg?branch=main)](https://github.com/colinc86/wrappederror/actions/workflows/go-test.yml) ![Go Coverage](https://img.shields.io/badge/Go%20Coverage-82%25-lightgreen.svg?style=flat) [![Go Reference](https://pkg.go.dev/badge/github.com/colinc86/wrappederror.svg)](https://pkg.go.dev/github.com/colinc86/wrappederror)\n\nPackage wrappederror implements an `error` type in Go for wrapping errors.\n\nIt contains handy methods to examine the error chain, stack and your source, and it plays nicely with other `error` types.\n\n- 🎁 [Wrapping Errors](#-wrapping-errors)\n- 🔍 [Examining Errors](#-examining-errors)\n  - 📏 [Depth](#-depth)\n  - 🔗 [Chain](#-chain)\n  - 👣 [Walk](#-walk)\n  - 🗺 [Trace](#-trace)\n  - 🖇 [Error and Context](#-error-and-context)\n  - 🗂 [Metadata](#-metadata)\n  - 📇 [Caller](#-caller)\n    - 📄 [File, Function and Line](#-file-function-and-line)\n    - 🧬 [Stack Trace](#-stack-trace)\n    - 🧩 [Source Fragments](#-source-fragments)\n  - 🔬 [Process](#-process)\n    - 💻 [Num Routines, CPUs and CGO](#-goroutines-cpus-and-cgo)\n    - 📊 [Memory Statistics](#-memory-statistics)\n    - 📌 [Programmatic Breakpoints](#-debugging)\n- 🚨 [Severity Detection](#-severity-detection)\n- 🧱 [Marshaling Errors](#-marshaling-errors)\n- 🗒 [Formatting Errors](#-formatting-errors)\n- 🎛 [Configuring Errors](#-configuring-errors)\n- 🧵 [Thread Safety](#-thread-safety)\n\n## Installing\n\nNavigate to your module and execute the following.\n\n```bash\n$ go get github.com/colinc86/wrappederror\n```\n\nImport the package:\n\n```go\nimport we \"github.com/colinc86/wrappederror\"\n```\n\n## 🎁 Wrapping Errors\n\nUse the package's `New` function to wrap errors and give them context.\n\n```go\n// Get an error\nerr := errors.New(\"some error\")\n\n// Wrap the error with some context\ne := we.New(err, \"oh no\")\n\n// Print the wrapped error\nfmt.Println(e)\n```\n\n```\noh no: some error\n```\n\nAn error's context doesn't have to be a string.\n\n```go\nmyObj := \u0026MyObj{}\nif data, err := json.Marshal(myObj); err != nil {\n  // If we failed to marshal myObj, then attach it to the error for context\n  return we.New(err, myObj)\n}\n```\n\n## 🔍 Examining Errors\n\nThere are many ways to examine an error...\n\n### 📏 Depth\n\nErrors have depth. That is, the number of errors, not including itself, in the error chain.\n\nFor eample, the following prints the depth of each error in the chain.\n\n```go\n// Create some errors\ne0 := we.New(nil, \"error A\")\ne1 := we.New(e0, \"error B\")\ne2 := we.New(e1, \"error C\")\n\n// Print their depths\nfmt.Printf(\"e0 depth: %d\\n\", e0.Depth())\nfmt.Printf(\"e1 depth: %d\\n\", e1.Depth())\nfmt.Printf(\"e2 depth: %d\\n\", e2.Depth())\n```\n\n```\ne0 depth: 0\ne1 depth: 1\ne2 depth: 2\n```\n\n### 🔗 Chain\n\nAccess the error chain as a flattened slice instead of wrapped errors using the `Chain` method.\n\n```go\n// Store the slice [e2, e1, e0] in c\nc := e2.Chain()\n```\n\nOptionally, directly access an error with a given depth or index.\n\n```go\n// Get the last error in the chain\nerrA := e2.ErrorWithDepth(0)\n\n// Get the first error in the chain\nerrB := e2.ErrorWithIndex(0)\n\n// Gets nil\nerrC := e2.ErrorWithDepth(3)\nerrD := e2.ErrorWithIndex(-1)\n```\n\n### 👣 Walk\n\nStep through the error chain with the `Walk` method. `Walk` calls the step function for every error in the chain until either the last error unwraps to `nil`, or the step function returns `false`.\n\n```go\ne2.Walk(func (err error) bool {\n  // Do something with the error\n\n  if err == ErrSomeParticularType {\n    // Don't continue with the walk\n    return false\n  }\n\n  if errors.Unwrap(err) == nil {\n    // This is the last error in the chain...\n    // Do something else\n  }\n\n  // Continue with the walk\n  return true\n})\n```\n\n### 🗺 Trace\n\nGet an error trace by calling the `Trace` method. This method returns a prettified string representation of the error chain with caller information. Errors in the chain not defined by this package log their depth and result of calling `Error`.\n\n```go\n// Print an error trace\nfmt.Println(e2.Trace())\n```\n\n```\n┌ 2: main.function (main.go:61) error C\n├ 1: main.function (main.go:60) error B\n└ 0: main.function (main.go:59) error A\n```\n\n### 🖇 Error and Context\n\nThe error's `Error` method returns an inline string representation of the entire error chain with each component separated by the characters `: ` (colon, space).\n\n```go\n// Print the entire error chain\nfmt.Println(e2.Error())\n```\n\n```\nerror C: error B: error A\n```\n\nTo only examine the receiver's context, use the `Context` method.\n\n```go\n// Only print the error's context\nfmt.Printf(\"%+v\", e2.Context())\n```\n\n```\nerror C\n```\n\n### 🗂 Metadata\n\nErrors come attached with metadata. `Metadata` types contain information about the error that can be useful when debugging such as\n\n- the severity of the error, if enabled, (see [Severity Detection](#-severity-detection)),\n- the error's index during the process's execution created by this package,\n- the number of similar non-nil errors that have been wrapped,\n- the duration since the process was launched and when the error was created,\n- and the time that the error was created.\n\n```go\n// Print the error's metadata\nfmt.Println(e.Metadata)\n```\n\n```\n[moderate] Network Timeout (#1) (≈0) (+10.000280) 2021-03-07 13:29:07.179446 -0600 CST m=+10.000589560\n```\n\nThe package keeps track of the number of similar errors by keeping a hash map of the errors that have been wrapped. It creates a 128-bit hash of an error's `Error` method and keeps a count of the number of identical hashes. You can turn this behavior on/off by using the `SetTrackSimilarErrors` configuration method.\n\n### 📇 Caller\n\nErrors capture call information accessible through the `Caller` property. Examine information such as code metadata, a stack trace and source fragment.\n\n#### 📄 File, Function and Line\n\n```go\n// Print call information\nfmt.Println(e2.Caller)\n```\n\n```\nmain.function (main.go:19)\n```\n\n#### 🧬 Stack Trace\n\nAlong with basic file, function and line information, you can use the caller to provide a stack trace of the goroutine the error was created on.\n\n```go\n// Print a stack trace\nfmt.Println(e.Caller.StackTrace)\n```\n\n```\ngoroutine 18 [running]:\nruntime/debug.Stack(0x0, 0x0, 0x0)\n  /usr/local/Cellar/go/1.16/libexec/src/runtime/debug/stack.go:24 +0xa5\ngithub.com/colinc86/wrappederror.currentCaller(0x1, 0x0)\n  /Users/colin/Documents/Programming/Go/wrappederror/caller.go:65 +0x45\ngithub.com/colinc86/wrappederror.TestStack(0xc000082600)\n  /Users/colin/Documents/Programming/Go/wrappederror/caller_test.go:25 +0x3f\ntesting.tRunner(0xc000082600, 0x11acff0)\n  /usr/local/Cellar/go/1.16/libexec/src/testing/testing.go:1194 +0x1a3\ncreated by testing.(*T).Run\n  /usr/local/Cellar/go/1.16/libexec/src/testing/testing.go:1239 +0x63c\n```\n\n#### 🧩 Source Fragments\n\nWhen possible, and permitted, the caller type also captures source code information.\n\n```go\n// Print the source code around the line that the error was created on\nfmt.Println(e2.Caller.Fragment)\n```\n\n```\n[47-51] /Users/colin/Documents/Programming/Go/wrappederror/wcaller_test.go\n\nfunc TestCallerSource(t *testing.T) {\n\tc := currentCaller(1)\n\tif c.Source() == \"\" {\n\t\tt.Error(\"Expected a source trace.\")\n\n```\n\nThe caller collects the immediate two lines above to two lines below the calling line. If you want more or less information you can set (and check) the radius with the `SetSourceFragmentRadius` and `SourceFragmentRadius` functions. You can also turn the feature off altogether with `SetCollectSourceFragments`.\n\n```go\n// If the radius hasn't been set to 5...\nif we.Config().SourceFragmentRadius() != 5 {\n  // Set the radius to 5\n  we.Config().SetSourceFragmentRadius(5)\n}\n```\n\n### 🔬 Process\n\nUse the error's `Process` property to get information about the current process at the time the error was created.\n\n#### 💻 Goroutines, CPUs and CGO\n\nProcess types contain some general process information like the number of current goroutines, the number of available CPUs, and the number of cgo functions executed.\n\n```go\n// Print the process information when the error was created\nfmt.Println(e.Process)\n```\n\n```\ngoroutines: 2, cpus: 16, cgos: 0\n```\n\n#### 📊 Memory Statistics\n\nMemory statistics are available with the `e.Process.Memory` property.\n\n```go\n// Print the allocated memory at the time of the error\nfmt.Printf(\"Allocated memory at %s: %d bytes\\n\", e.Metadata.Time, e.Process.Memory.Alloc)\n```\n\n#### 📌 Debugging\n\nIt is also possible to trigger a breakpoint programatically when an error is received using the `Process` type.\n\n```go\n// doSomething returns a wrapped error\nif e := doSomething(); e != nil {\n  // Initiate a breakpoint\n  e.Process.Break()\n\n  // Continue\n  return we\n}\n```\n\nAll calls to `Process.Break()` are ignored by default. A call to the configuration's `SetIgnoreBreakpoints` with a value of `false` must happen before `Process` types will attempt to break.\n\n```go\n// Ignore all breakpoints if we aren't debugging\nwe.Config().SetIgnoreBreakpoints(os.Getenv(\"DEBUG\") != \"true\")\n\ne := New(nil, err)\n\n// Only attempts to break if the env var DEBUG is \"true\"\ne.Break()\n```\n\n## 🚨 Severity Detection\n\nThe package can detect the severity of newly wrapped errors using a table of registered `ErrorSeverity` types. The package matches the severity's regular expression against the output of each error's `Error` method in the error chain. A score in the interval [0.0, 1.0] is calculated by calculating the ratio of the number of matched characters in the string to the total number of characters in the string.\n\nTo register a new error severity, first create a new instance of the structure such that no error is returned.\n\n```go\n// Create two error severities\ns1, err := we.NewErrorSeverity(\"Network Timeout\", \"i/o timeout\", we.ErrorSeverityLevelModerate)\nif err != nil {\n  fmt.Printf(\"Invalid regex: %s\\n\", err)\n}\n\ns2, err := we.NewErrorSeverity(\"🚨\", \"fail\", we.ErrorSeverityLevelHigh)\nif err != nil {\n  fmt.Printf(\"Invalid regex: %s\\n\", err)\n}\n```\n\nand then register the error severity with the package.\n\n```go\nif err := we.RegisterErrorSeverity(s1); err != nil {\n  fmt.Printf(\"Unable to register error severity: %s\\n\", err)\n}\n\nif err := we.RegisterErrorSeverity(s2); err != nil {\n  fmt.Printf(\"Unable to register error severity: %s\\n\", err)\n}\n```\n\nNow, when new errors are created, they will be matched against the registered error severities and the error's `Metadata.Severity` may contain a non-nil value.\n\n```go\n// Got a network timeout\ne1 := errors.New(\"dial tcp 0.0.0.0:3000: i/o timeout\")\ne2 := New(e1, \"get request failed\")\nif e2.Metadata.Severity != nil {\n  fmt.Println(e2.Metadata.Severity)\n}\n\n// Got an error saving a file\ne3 := errors.New(\"save failed because file does not exist\")\ne4 := New(e3, \"unable to save file\")\ne5 := New(e4, \"file error\")\nif e5.Metadata.Severity != nil {\n  fmt.Println(e5.Metadata.Severity)\n}\n```\n\n```\n[moderate] Network Timeout\n[high] 🚨\n```\n\nTo unregister error severities, call the `UnregisterErrorSeverity` function with the severity you want to unregister.\n\n```go\nwe.UnregisterErrorSeverity(s1)\nwe.UnregisterErrorSeverity(s2)\n```\n\nThe available `ErrorSeverityLevel` constants are\n\n| Level                        |\n|:-----------------------------|\n| `ErrorSeverityLevelNone`     |\n| `ErrorSeverityLevelLow`      |\n| `ErrorSeverityLevelModerate` |\n| `ErrorSeverityLevelHigh`     |\n| `ErrorSeverityLevelSevere`   |\n\n## 🧱 Marshaling Errors\n\nThe package supports marshaling errors into JSON, but because the error type defined in this package wraps errors of type `error`, a bijective `UnmarshalJSON` method isn't possible. Intead of attempting to guess at wrapped types, the package just doesn't try.\n\nThe types `Caller`, `Process`, `Metadata` and `ErrorSeverity` _do_ implement both JSON marshaling and unmarshaling.\n\nThe error chain can get long, and if errors are collecting caller and process information, then JSON objects for a \"single\" top-level error may be disproportionately large compared to the rest of the JSON object they're embedded in. The package provides a method for determining how errors are marshaled in to JSON data.\n\n```go\n// Marshal full JSON objects\nwe.Config().SetMarshalMinimalJSON(false)\n\n// Marshal a slimmed-down version of errors\nwe.Config().SetMarshalMinimalJSON(true)\n```\n\nThe package marshals its error type in to one of two versions of JSON (defined by the `MarshalMinimalJSON` configuration value):\n\n```jsonc\n// The \"full\" version of an error\n{\n  \"context\": \"the error's context\",\n  \"depth\": 0,\n  \"wraps\": { /* another error or null */ },\n  \"caller\": {\n    \"file\": \"/path/to/file\",\n    \"function\": \"function\",\n    \"line\": 0,\n    \"stackTrace\": \"trace\",\n    \"sourceFragment:\" \"source\"\n  },\n  \"process\": {\n    \"goroutines\": 1,\n    \"cpus\": 8,\n    \"cgos\": 0,\n    \"memory\": { /* runtime.MemStats */ },\n  },\n  \"metadata\": {\n    \"time\": \"the time\",\n    \"duration\": 0.0,\n    \"index\": 0,\n    \"similar\": 0\n  }\n}\n```\n\n```jsonc\n// The \"minimal\" version of an error\n{\n  \"context\": \"the error's context\",\n  \"depth\": 0,\n  \"wraps\": { /* another error or null */ },\n  \"time\": \"the time\",\n  \"duration\": 0.0,\n  \"index\": 0,\n  \"similar\": 0,\n  \"file\": \"/path/to/file\",\n  \"function\": \"function\",\n  \"line\": 0\n}\n```\n\nAll other errors are marshaled in to a generic JSON object:\n\n```jsonc\n// The \"generic\" version of an error\n{\n  \"error\": \"the output of Error() string\",\n  \"wraps\": { /* another error or null */ }\n}\n```\n\n## 🗒 Formatting Errors\n\nErrors have a `Format` method that returns a string with a custom format. It takes an error format string, `ef`, that is built using error format tokens.\n\nFor example, you can achieve the same output as the caller's description by using the following format,\n\n```go\nef := fmt.Sprintf(\n  \"%s (%s:%s)\",\n  ErrorFormatTokenFunction,\n  ErrorFormatTokenFile,\n  ErrorFormatTokenLine,\n)\n\n// The following statments have the same output\nfmt.Println(e.Format(ef))\nfmt.Println(e.Caller)\n```\n\nor you can create more complex/custom error formats.\n\n```go\nef := fmt.Sprintf(\n  \"Error #%s at %s (%s:%s): %s\",\n  ErrorFormatTokenIndex,\n  ErrorFormatTokenTime,\n  ErrorFormatTokenFile,\n  ErrorFormatTokenLine,\n  ErrorFormatTokenChain,\n)\n\nfmt.Println(e.Format(ef))\n```\n\n```\nError #2 at 2021-03-07 16:39:56.393366 -0600 CST m=+0.001129674 (formatter_test.go:10): error 2: error 1\n```\n\nThe available tokens are as follows.\n\n| Token                           | Description |\n|:--------------------------------|:------------|\n| `ErrorFormatTokenContext`       | The error's context. |\n| `ErrorFormatTokenInner`         | The output of the inner error's `Error` method. |\n| `ErrorFormatTokenChain`         | The error chain as returned by the error's `Error` method. |\n| `ErrorFormatTokenFile`          | The file name from the error's caller. |\n| `ErrorFormatTokenFunction`      | The function from the error's caller. |\n| `ErrorFormatTokenLine`          | The line number from the error's caller. |\n| `ErrorFormatTokenStack`         | The stack trace from the error's caller. |\n| `ErrorFormatTokenSource`        | The source fragment from the error's caller. |\n| `ErrorFormatTokenTime`          | The time from the error's metadata. |\n| `ErrorFormatTokenDuration`      | The duration (in seconds) from the error's metadata. |\n| `ErrorFormatTokenIndex`         | The error's index. |\n| `ErrorFormatTokenSimilar`       | The number of similar errors. |\n| `ErrorFormatTokenRoutines`      | The number of goroutines when the error was created. |\n| `ErrorFormatTokenCPUs`          | The number of available CPUs when the error was created. |\n| `ErrorFormatTokenCGO`           | The number of cgo calls when the error was created. |\n| `ErrorFormatTokenMemory`        | The process memory statistics when the error was created. |\n| `ErrorFormatTokenSeverityTitle` | The detected error severity title. |\n| `ErrorFormatTokenSeverityLevel` | The detected error severity level. |\n\n## 🎛 Configuring Errors\n\nThe package's configuration is accessible through the global `Config` function.\n\nUse the `Set` method to configure everything at once, or any of the corresponding setters to the getters listed in the table below.\n\n```go\n// Configure the package to\n// - Capture call information\n// - Ignore process information\n// - Collect source fragments\n// - Get 9 lines of source\n// - Ignore breakpoints\n// - Start indexing errors at 1\n// - Track similar errors\n// - Marshal full errors in to JSON\nwe.Config().Set(true, false, true, 4, true, 1, true, false)\n```\n\nTo return to the initial state upon launch, use the `ResetState` function. Resetting state resets all configuration variables, the process launch time, and the hash map for keeping track for similar wrapped errors.\n\n```go\n// Reset the package's state and configuration.\nwe.ResetState()\n```\n\n| Function                     | Initial Value | Description |\n|:-----------------------------|:--------------|:------------|\n| `CaptureCaller() bool`       | `true`        | Determines whether or not new errors will capture their call information. If you don't need to capture call information, you can set this to `false`. Be advised, future calls to `Caller` on new errors will return `nil`. |\n| `CaptureProcess() bool`      | `true`        | Determines whether or not new errors will capture process information. If you don't need to capture process information, you can set this to `false`. Same as `CaptureCaller`, future calls to `Process` on new errors will return `nil`. |\n| `CaptureSourceFragments`     | `true`        | Determines whether or not new errors will capture source code around the line that the error was created on. |\n| `SourceFragmentRadius() int` | `2`           | The line radius of source fragments collected during debugging. For example, if the error is created on line 15 in a file, then (using the default radius of 2) source would be collected from lines 13 through 17. |\n| `IgnoreBreakpoints() bool`   | `true`        | Determines whether or not breakpoints should be ignored when calling `Process.Break`. |\n| `NextErrorIndex() int`       | `1`           | The next index that will be used when creating an error in the error's metadata. |\n| `TrackSimilarErrors() bool`  | `true`        | Whether or not errors that are wrapped should be tracked for similarity. |\n| `MarshalMinimalJSON() bool`  | `true`        | Determines how errors are marshaled in to JSON. When this value is true, a smaller JSON object is created without size-inflating data like stack traces and source fragments. |\n\n## 🧵 Thread Safety\n\nThe package was built with thread-safety in mind. You can modify configuration settings and create errors from any goroutine without worrying about locks.\n\n## 👥 Contributing\n\nFeel free to contribute either through reporting issues or submitting pull requests.\n\nThank you to @GregWWalters for ideas, tips and advice.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcolinc86%2Fwrappederror","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcolinc86%2Fwrappederror","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcolinc86%2Fwrappederror/lists"}