{"id":13412778,"url":"https://github.com/samber/oops","last_synced_at":"2025-05-15T12:06:17.775Z","repository":{"id":165896249,"uuid":"641292359","full_name":"samber/oops","owner":"samber","description":"🔥 Error handling library with context, assertion, stack trace and source fragments","archived":false,"fork":false,"pushed_at":"2025-05-05T20:20:13.000Z","size":2233,"stargazers_count":645,"open_issues_count":16,"forks_count":22,"subscribers_count":8,"default_branch":"main","last_synced_at":"2025-05-07T22:39:59.431Z","etag":null,"topics":["assert","assertion","attributes","context","error","exception","go","handling","logger","logrus","logrus-fomatter","slog","stacktrace","structured-logging","zap","zerolog"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/samber/oops","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/samber.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["samber"]}},"created_at":"2023-05-16T07:12:16.000Z","updated_at":"2025-05-06T15:30:04.000Z","dependencies_parsed_at":null,"dependency_job_id":"753eb27e-dc4c-4e0b-bdae-692cb4fd5d43","html_url":"https://github.com/samber/oops","commit_stats":null,"previous_names":[],"tags_count":31,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samber%2Foops","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samber%2Foops/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samber%2Foops/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samber%2Foops/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/samber","download_url":"https://codeload.github.com/samber/oops/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254337613,"owners_count":22054253,"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":["assert","assertion","attributes","context","error","exception","go","handling","logger","logrus","logrus-fomatter","slog","stacktrace","structured-logging","zap","zerolog"],"created_at":"2024-07-30T20:01:29.083Z","updated_at":"2025-05-15T12:06:17.256Z","avatar_url":"https://github.com/samber.png","language":"Go","readme":"\n# Oops - Error handling with context, assertion, stack trace and source fragments\n\n[![tag](https://img.shields.io/github/tag/samber/oops.svg)](https://github.com/samber/oops/releases)\n![Go Version](https://img.shields.io/badge/Go-%3E%3D%201.21-%23007d9c)\n[![GoDoc](https://godoc.org/github.com/samber/oops?status.svg)](https://pkg.go.dev/github.com/samber/oops)\n![Build Status](https://github.com/samber/oops/actions/workflows/test.yml/badge.svg)\n[![Go report](https://goreportcard.com/badge/github.com/samber/oops)](https://goreportcard.com/report/github.com/samber/oops)\n[![Coverage](https://img.shields.io/codecov/c/github/samber/oops)](https://codecov.io/gh/samber/oops)\n[![Contributors](https://img.shields.io/github/contributors/samber/oops)](https://github.com/samber/oops/graphs/contributors)\n[![License](https://img.shields.io/github/license/samber/oops)](./LICENSE)\n\n(Yet another) error handling library: `oops.Errorf` is a dead-simple drop-in replacement for built-in `error`, adding contextual information such as stack trace, extra attributes, error code, and bug-fixing hints...\n\n\u003e [!WARNING]  \n\u003e This is NOT a logging library. `oops` should complement your existing logging toolchain (zap, zerolog, logrus, slog, go-sentry...).\n\n🥷 Start hacking `oops` with this [playground](https://go.dev/play/p/-_7EBnceJ_A).\n\n\u003cdiv align=\"center\"\u003e\n  \u003chr\u003e\n  \u003csup\u003e\u003cb\u003eSponsored by:\u003c/b\u003e\u003c/sup\u003e\n  \u003cbr\u003e\n  \u003ca href=\"https://quickwit.io?utm_campaign=github_sponsorship\u0026utm_medium=referral\u0026utm_content=samber-oops\u0026utm_source=github\"\u003e\n    \u003cdiv\u003e\n      \u003cimg src=\"https://github.com/samber/oops/assets/2951285/49aaaa2b-b8c6-4f21-909f-c12577bb6a2e\" width=\"240\" alt=\"Quickwit\"\u003e\n    \u003c/div\u003e\n    \u003cdiv\u003e\n      Cloud-native search engine for observability - An OSS alternative to Splunk, Elasticsearch, Loki, and Tempo.\n    \u003c/div\u003e\n  \u003c/a\u003e\n  \u003chr\u003e\n\u003c/div\u003e\n\n\u003cimg align=\"right\" title=\"Oops gopher logo\" alt=\"logo: thanks Gimp\" width=\"280\" src=\"assets/logo.png\"\u003e\n\nJump:\n\n- [🤔 Motivations](#-motivations)\n- [🚀 Install](#-install)\n- [💡 Quick start](#-quick-start)\n- [🧠 Spec](#-spec)\n  - [Error constructors](#error-constructors)\n  - [Context](#context)\n  - [Other helpers](#other-helpers)\n  - [Stack trace](#stack-trace)\n  - [Source fragments](#source-fragments)\n  - [Panic handling](#panic-handling)\n  - [Assertions](#assertions)\n  - [Output](#output)\n  - [Go context](#go-context)\n- [📫 Loggers](#-loggers)\n- [🥷 Tips and best practices](#-tips-and-best-practices)\n\t\n## 🤔 Motivations\n\nLoggers usually allow developers to build records with contextual attributes, that describe errors, such as:\n- `zap.Infow(\"failed to fetch URL\", \"url\", url)`\n- `logrus.WithFields(\"url\", url).Error(\"failed to fetch URL\")`).\n\nGo recommends cascading error handling, which can cause the error to be triggered far away from the call to the logger. Returning context over X callers is painful, and to be meaningful, the stack trace must be gathered by the error builder instead of the logger.\n\nThis is why we need an `error` wrapper!\n\n🥵 Why develop yet another library?\n- drop-in replacement to `error`\n- easy to integrate without large refactoring\n- separation of concern (logger vs error)\n- extra attributes\n- developer-friendly error builder\n- no extra code for [output](#output): can be used with loggers, printf syntax...\n- out-of-the-box stack trace and source fragments\n- transport error builder in a Go context\n- one-line panic handling\n- one-line assertion\n\n### ❌ Before samber/oops\n\nIn the following example, we try to propagate an error with contextual information and stack trace, to the caller function `handler()`:\n\n```go\nfunc c(token string) error {\n    userID := ...   // \u003c-- How do I transport `userID` and `role` from here...\n    role := ...\n\n    // ...\n\n    return fmt.Errorf(\"an error\")\n}\n\nfunc b() error {\n    // ...\n    return c()\n}\n\nfunc a() {\n    err := b()\n    if err != nil {\n        // print log\n        slog.Error(err.Error(),\n            slog.String(\"user.id\", \"????\"),      // \u003c-- ...to here ??\n            slog.String(\"user.role\", \"????\"),    // \u003c-- ...and here ??\n            slog.String(\"stracktrace\", generateStacktrace()))  // \u003c-- this won't contain the exact error location 😩\n    }\n}\n```\n\n### ✅ Using samber/oops\n\nI would rather write something like that:\n\n```go\nfunc d() error {\n    return oops.\n        Code(\"iam_missing_permission\").\n        In(\"authz\").\n        Tags(\"authz\").\n        Time(time.Now()).\n        With(\"user_id\", 1234).\n        With(\"permission\", \"post.create\").\n        Hint(\"Runbook: https://doc.acme.org/doc/abcd.md\").\n        User(\"user-123\", \"firstname\", \"john\", \"lastname\", \"doe\").\n        Errorf(\"permission denied\")\n}\n\nfunc c() error {\n\treturn d()\n}\n\nfunc b() error {\n    // add more context\n    return oops.\n        In(\"iam\").\n        Tags(\"iam\").\n        Trace(\"e76031ee-a0c4-4a80-88cb-17086fdd19c0\").\n        With(\"hello\", \"world\").\n        Wrapf(c(), \"something failed\")\n}\n\nfunc a() error {\n\treturn b()\n}\n\nfunc main() {\n    logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))\n\n    err := a()\n    if err != nil {\n        logger.Error(\n            err.Error(),\n            slog.Any(\"error\", err), // unwraps and flattens error context\n        )\n    }\n}\n```\n\n\u003cdiv style=\"text-align:center;\"\u003e\n    \u003cimg alt=\"Why 'oops'?\" src=\"./assets/motivation.png\" style=\"max-width: 650px;\"\u003e\n\u003c/div\u003e\n\n### Why \"oops\"?\n\nHave you already heard a developer yelling at unclear error messages in Sentry, with no context, just before figuring out he wrote this piece of shit by himself?\n\nYes. Me too.\n\n\u003cdiv style=\"text-align:center;\"\u003e\n    \u003cimg alt=\"oops!\" src=\"https://media3.giphy.com/media/v1.Y2lkPTc5MGI3NjExZDU2MjE1ZTk1ZjFmMWNkOGZlY2YyZGYzNjA4ZWIyZWU4NTI3MmE1OCZlcD12MV9pbnRlcm5hbF9naWZzX2dpZklkJmN0PWc/mvyvXwL26FfAtRCLPk/giphy.gif\"\u003e\n\u003c/div\u003e\n\n## 🚀 Install\n\n```sh\ngo get github.com/samber/oops\n```\n\nThis library is v1 and follows SemVer strictly.\n\nNo breaking changes will be made to APIs before v2.0.0.\n\nThis library has no dependencies outside the Go standard library.\n\n## 💡 Quick start\n\nThis library provides a simple `error` builder for composing structured errors, with contextual attributes and stack trace.\n\nSince `oops.OopsError` implements the `error` interface, you will be able to compose and wrap native errors with `oops.OopsError`.\n\n🥷 Start hacking `oops` with this [playground](https://go.dev/play/p/-_7EBnceJ_A).\n\n## 🧠 Spec\n\nGoDoc: [https://godoc.org/github.com/samber/oops](https://godoc.org/github.com/samber/oops)\n\n### Error constructors\n\n| Constructor                                                             | Description                                                                                           |\n| ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |\n| `.New(message string) error`                                            | Formats returns `oops.OopsError` object that satisfies `error`                                        |\n| `.Errorf(format string, args ...any) error`                             | Formats an error and returns `oops.OopsError` object that satisfies `error`                           |\n| `.Wrap(err error) error`                                                | Wraps an error into an `oops.OopsError` object that satisfies `error`                                 |\n| `.Wrapf(err error, format string, args ...any) error`                   | Wraps an error into an `oops.OopsError` object that satisfies `error` and formats an error message    |\n| `.Recover(cb func()) error`                                             | Handle panic and returns `oops.OopsError` object that satisfies `error`.                              |\n| `.Recoverf(cb func(), format string, args ...any) error`                | Handle panic and returns `oops.OopsError` object that satisfies `error` and formats an error message. |\n| `.Assert(condition bool) OopsErrorBuilder`                              | Panics if condition is false. Assertions can be chained.                                              |\n| `.Assertf(condition bool, format string, args ...any) OopsErrorBuilder` | Panics if condition is false and formats an error message. Assertions can be chained.                 |\n| `.Join(err1 error, err2 error, ...) error`                              | Join returns an error that wraps the given errors.                                                    |\n\n#### Examples\n\n```go\n// with error wrapping\nerr0 := oops.\n    In(\"repository\").\n    Tags(\"database\", \"sql\").\n    Wrapf(sql.Exec(query), \"could not fetch user\")  // Wrapf returns nil when sql.Exec() is nil\n\n// with panic recovery\nerr1 := oops.\n    In(\"repository\").\n    Tags(\"database\", \"sql\").\n    Recover(func () {\n        panic(\"caramba!\")\n    })\n\n// with assertion\nerr2 := oops.\n    In(\"repository\").\n    Tags(\"database\", \"sql\").\n    Recover(func () {\n        // ...\n        oops.Assertf(time.Now().Weekday() == 1, \"This code should run on Monday only.\")\n        // ...\n    })\n\n// oops.New\nerr3 := oops.\n    In(\"repository\").\n    Tags(\"database\", \"sql\").\n    New(\"an error message\")\n\n// oops.Errorf\nerr3 := oops.\n    In(\"repository\").\n    Tags(\"database\", \"sql\").\n    Errorf(\"an error message: %d\", 42)\n```\n\n### Context\n\nThe library provides an error builder. Each method can be used standalone (eg: `oops.With(...)`) or from a previous builder instance (eg: `oops.In(\"iam\").User(\"user-42\")`).\n\nThe `oops.OopsError` builder must finish with either `.Errorf(...)`, `.Wrap(...)`, `.Wrapf(...)`, `.Join(...)`, `.Recover(...)` or `.Recoverf(...)`.\n\n| Builder method                          | Getter                                  | Description                                                                                                                                                                                |\n|-----------------------------------------|-----------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `.With(string, any)`                    | `err.Context() map[string]any`          | Supply a list of attributes key+value. Values of type `func() any {}` are accepted and evaluated lazily.                                                                                   |\n| `.WithContext(context.Context, ...any)` | `err.Context() map[string]any`          | Supply a list of values declared in context. Values of type `func() any {}` are accepted and evaluated lazily.                                                                             |\n| `.Code(string)`                         | `err.Code() string`                     | Set a code or slug that describes the error. Error messages are intended to be read by humans, but such code is expected to be read by machines and be transported over different services |\n| `.Public(string)`                       | `err.Public() string`                   | Set a message that is safe to show to an end user                                                                                                                                          |\n| `.Time(time.Time)`                      | `err.Time() time.Time`                  | Set the error time (default: `time.Now()`)                                                                                                                                                 |\n| `.Since(time.Time)`                     | `err.Duration() time.Duration`          | Set the error duration                                                                                                                                                                     |\n| `.Duration(time.Duration)`              | `err.Duration() time.Duration`          | Set the error duration                                                                                                                                                                     |\n| `.In(string)`                           | `err.Domain() string`                   | Set the feature category or domain                                                                                                                                                         |\n| `.Tags(...string)`                      | `err.Tags() []string`                   | Add multiple tags, describing the feature returning an error                                                                                                                               |\n|                                         | `err.HasTag(string) bool`               | Check whether the error contains provided tag                                                                                                                                              |\n| `.Trace(string)`                        | `err.Trace() string`                    | Add a transaction id, trace id, correlation id... (default: ULID)                                                                                                                          |\n| `.Span(string)`                         | `err.Span() string`                     | Add a span representing a unit of work or operation... (default: ULID)                                                                                                                     |\n| `.Hint(string)`                         | `err.Hint() string`                     | Set a hint for faster debugging                                                                                                                                                            |\n| `.Owner(string)`                        | `err.Owner() (string)`                  | Set the name/email of the colleague/team responsible for handling this error. Useful for alerting purpose                                                                                  |\n| `.User(string, any...)`                 | `err.User() (string, map[string]any)`   | Supply user id and a chain of key/value                                                                                                                                                    |\n| `.Tenant(string, any...)`               | `err.Tenant() (string, map[string]any)` | Supply tenant id and a chain of key/value                                                                                                                                                  |\n| `.Request(*http.Request, bool)`         | `err.Request() *http.Request`           | Supply http request                                                                                                                                                                        |\n| `.Response(*http.Response, bool)`       | `err.Response() *http.Response`         | Supply http response                                                                                                                                                                       |\n| `.FromContext(context.Context)`         |                                         | Reuse an existing OopsErrorBuilder transported in a Go context                                                                                                                             |\n\n#### Examples\n\n```go\n// simple error with public facing message\nerr0 := oops.\n    Public(\"Could not fetch user.\").\n    Errorf(\"sql: bad connection\")\n\n// simple error with stacktrace\nerr1 := oops.New(\"could not fetch user\")\n\n// with optional domain\nerr2 := oops.\n    In(\"repository\").\n    Tags(\"database\", \"sql\").\n    Errorf(\"could not fetch user\")\n\n// with custom attributes\nctx := context.WithContext(context.Background(), \"a key\", \"value\")\nerr3 := oops.\n    With(\"driver\", \"postgresql\").\n    With(\"query\", query).\n    With(\"query.duration\", queryDuration).\n    With(\"lorem\", func() string { return \"ipsum\" }).\t// lazy evaluation\n    WithContext(ctx, \"a key\", \"another key\").\n    Errorf(\"could not fetch user\")\n\n// with trace+span\nerr4 := oops.\n    Trace(traceID).\n    Span(spanID).\n    Errorf(\"could not fetch user\")\n\n// with hint and ownership, for helping developer to solve the issue\nerr5 := oops.\n    Hint(\"The user could have been removed. Please check deleted_at column.\").\n    Owner(\"Slack: #api-gateway\").\n    Errorf(\"could not fetch user\")\n\n// with optional userID\nerr6 := oops.\n    User(userID).\n    Errorf(\"could not fetch user\")\n\n// with optional user data\nerr7 := oops.\n    User(userID, \"firstname\", \"Samuel\").\n    Errorf(\"could not fetch user\")\n\n// with optional user and tenant\nerr8 := oops.\n    User(userID, \"firstname\", \"Samuel\").\n    Tenant(workspaceID, \"name\", \"my little project\").\n    Errorf(\"could not fetch user\")\n\n// with optional http request and response\nerr9 := oops.\n    Request(req, false).\n    Response(res, true).\n    Errorf(\"could not fetch user\")\n\n// reuse an existing OopsErrorBuilder transported in a Go context\nctx := oops.WithBuilder(context.TODO(), err9)\n// [...]\nerr10 := oops.\n    FromContext(ctx).\n    Errorf(\"could not fetch user\")\n```\n\n### Other helpers\n\n- `oops.AsError[MyError](error) (MyError, bool)` as an alias to `errors.As(...)`\n\n### Stack trace\n\nThis library provides a pretty printed stack trace for each generated error.\n\nThe stack trace max depth can be set using:\n\n```go\n// default: 10\noops.StackTraceMaxDepth = 42\n```\n\nThe stack trace will be printed this way:\n\n```go\nerr := oops.Errorf(\"permission denied\")\n\nfmt.Println(err.(oops.OopsError).Stacktrace())\n```\n\n\u003cdiv style=\"text-align:center;\"\u003e\n    \u003cimg alt=\"Stacktrace\" src=\"./assets/stacktrace1.png\" style=\"max-width: 650px;\"\u003e\n\u003c/div\u003e\n\nWrapped errors will be reported as an annotated stack trace:\n\n```go\nerr1 := oops.Errorf(\"permission denied\")\n// ...\nerr2 := oops.Wrapf(err1, \"something failed\")\n\nfmt.Println(err2.(oops.OopsError).Stacktrace())\n```\n\n\u003cdiv style=\"text-align:center;\"\u003e\n    \u003cimg alt=\"Stacktrace\" src=\"./assets/stacktrace2.png\" style=\"max-width: 650px;\"\u003e\n\u003c/div\u003e\n\n### Source fragments\n\nThe exact error location can be provided in a Go file extract.\n\nSource fragments are hidden by default. You must run `oops.SourceFragmentsHidden = false` to enable this feature. Go source files being read at run time, you have to keep the source code at the same location.\n\nIn a future release, this library is expected to output a colorized extract. Please contribute!\n\n```go\noops.SourceFragmentsHidden = false\n\nerr1 := oops.Errorf(\"permission denied\")\n// ...\nerr2 := oops.Wrapf(err, \"something failed\")\n\nfmt.Println(err2.(oops.OopsError).Sources())\n```\n\n\u003cdiv style=\"text-align:center;\"\u003e\n    \u003cimg alt=\"Sources\" src=\"./assets/sources1.png\" style=\"max-width: 650px;\"\u003e\n\u003c/div\u003e\n\n### Panic handling\n\n`oops` library is delivered with a try/catch -ish error handler. 2 handlers variants are available: `oops.Recover()` and `oops.Recoverf()`. Both can be used in the `oops` error builder with usual methods.\n\n🥷 Start hacking `oops.Recover()` with this [playground](https://go.dev/play/p/uGwrFj9mII8).\n\n```go\nfunc mayPanic() {\n\tpanic(\"permission denied\")\n}\n\nfunc handlePanic() error {\n    return oops.\n        Code(\"iam_authz_missing_permission\").\n        In(\"authz\").\n        With(\"permission\", \"post.create\").\n        Trace(\"6710668a-2b2a-4de6-b8cf-3272a476a1c9\").\n        Hint(\"Runbook: https://doc.acme.org/doc/abcd.md\").\n        Recoverf(func() {\n            // ...\n            mayPanic()\n            // ...\n        }, \"unexpected error %d\", 42)\n}\n```\n\n### Assertions\n\nAssertions may be considered an anti-pattern for Golang since we only call `panic()` for unexpected and critical errors. In this situation, assertions might help developers to write safer code.\n\n```go\nfunc mayPanic() {\n    x := 42\n\n    oops.\n        Trace(\"6710668a-2b2a-4de6-b8cf-3272a476a1c9\").\n        Hint(\"Runbook: https://doc.acme.org/doc/abcd.md\").\n        Assertf(time.Now().Weekday() == 1, \"This code should run on Monday only.\").\n        With(\"x\", x).\n        Assertf(x == 42, \"expected x to be equal to 42, but got %d\", x)\n\n    oops.Assert(re.Match(email))\n\n    // ...\n}\n\nfunc handlePanic() error {\n    return oops.\n        Code(\"iam_authz_missing_permission\").\n        In(\"authz\").\n        Recover(func() {\n            // ...\n            mayPanic()\n            // ...\n        })\n}\n```\n\n### Output\n\nErrors can be printed in many ways. Logger formatters provided in this library use these methods.\n\n#### Errorf `%w`\n\n```go\nstr := fmt.Errorf(\"something failed: %w\", oops.Errorf(\"permission denied\"))\n\nfmt.Println(err.Error())\n// Output:\n// something failed: permission denied\n```\n\n#### printf `%v`\n\n```go\nerr := oops.Errorf(\"permission denied\")\n\nfmt.Printf(\"%v\", err)\n// Output:\n// permission denied\n```\n\n#### printf `%+v`\n\n```go\nerr := oops.Errorf(\"permission denied\")\n\nfmt.Printf(\"%+v\", err)\n```\n\n\u003cdiv style=\"text-align:center;\"\u003e\n    \u003cimg alt=\"Output\" src=\"./assets/output-printf-plusv.png\" style=\"max-width: 650px;\"\u003e\n\u003c/div\u003e\n\n#### JSON Marshal\n\n```go\nb := json.MarshalIndent(err, \"\", \"  \")\n```\n\n\u003cdiv style=\"text-align:center;\"\u003e\n    \u003cimg alt=\"Output\" src=\"./assets/output-json.png\" style=\"max-width: 650px;\"\u003e\n\u003c/div\u003e\n\n#### slog.Valuer\n\n```go\nerr := oops.Errorf(\"permission denied\")\n\nattr := slog.Error(err.Error(),\n    slog.Any(\"error\", err))\n\n// Output:\n// slog.Group(\"error\", ...)\n```\n\n#### Custom timezone\n\n```go\nloc, _ := time.LoadLocation(\"Europe/Paris\")\noops.Local = loc\n```\n\n### Go context\n\nAn `OopsErrorBuilder` can be transported in a go `context.Context` to reuse later.\n\n```go\nfunc myFunc(ctx context.Context) {\n    oops.\n        FromContext(ctx).\n        Tag(\"auth\").\n        Errorf(\"not permitted\")\n}\n\nfunc main() {\n    err := oops.\n        In(\"my domain\").\n        User(\"user-123\")\n    ctx := oops.WithBuilder(context.TODO(), err)\n\n    myFunc(ctx)\n}\n```\n\n## 📫 Loggers\n\nSome loggers may need a custom formatter to extract attributes from `oops.OopsError`.\n\nAvailable loggers:\n- log: [playground](https://go.dev/play/p/uNx3CcT-X40) - [example](https://github.com/samber/oops/tree/master/examples/log)\n- slog: [playground](https://go.dev/play/p/-X2ZnqjyDLu) - [example](https://github.com/samber/oops/tree/master/examples/slog)\n- logrus: [formatter](https://github.com/samber/oops/tree/master/loggers/logrus) - [playground](https://go.dev/play/p/-_7EBnceJ_A) - [example](https://github.com/samber/oops/tree/master/examples/logrus)\n- zerolog: [formatter](https://github.com/samber/oops/tree/master/loggers/zerolog) - [playground](https://go.dev/play/p/aalqQ6wEDyx) - [example](https://github.com/samber/oops/tree/master/examples/zerolog)\n\nWe are looking for contributions and examples for:\n- zap\n- go-sentry\n- otel\n- other?\n\nExamples of formatters can be found in `ToMap()`, `Format()`, `Marshal()` and `LogValuer` methods of `oops.OopsError`.\n\n## 🥷 Tips and best practices\n\n### Public facing error message\n\nHumans do not like technical errors. The `oops` container can bring an additional human-readable message.\n\n```go\nerr := oops.\n    Public(\"Could not fetch user.\").\n    Errorf(\"sql: bad connection\")\n\nuserMessage := oops.GetPublic(err, \"Unexpected error\")\n```\n\n### Wrap/Wrapf shortcut\n\n`oops.Wrap(...)` and `oops.Wrapf(...)` returns nil if the provided `error` is nil.\n\n❌ So don't write:\n\n```go\nerr := mayFail()\nif err != nil {\n    return oops.Wrapf(err, ...)\n}\n\nreturn nil\n```\n\n✅ but write:\n\n```go\nreturn oops.Wrapf(mayFail(), ...)\n```\n\n### Reuse error builder\n\nWriting a full contextualized error can be painful and very repetitive. But a single context can be used for multiple errors in a single function:\n\n❌ So don't write:\n\n```go\nerr := mayFail1()\nif err != nil {\n    return oops.\n        In(\"iam\").\n        Trace(\"77cb6664\").\n        With(\"hello\", \"world\").\n        Wrap(err)\n}\n\nerr = mayFail2()\nif err != nil {\n    return oops.\n        In(\"iam\").\n        Trace(\"77cb6664\").\n        With(\"hello\", \"world\").\n        Wrap(err)\n}\n\nreturn oops.\n    In(\"iam\").\n    Trace(\"77cb6664\").\n    With(\"hello\", \"world\").\n    Wrap(mayFail3())\n```\n\n✅ but write:\n\n```go\nerrorBuilder := oops.\n    In(\"iam\").\n    Trace(\"77cb6664\").\n    With(\"hello\", \"world\")\n\nerr := mayFail1()\nif err != nil {\n    return errorBuilder.Wrap(err)\n}\n\nerr = mayFail2()\nif err != nil {\n    return errorBuilder.Wrap(err)\n}\n\nreturn errorBuilder.Wrap(mayFail3())\n```\n\n### Caller/callee attributes\n\nAlso, think about feeding error context in every caller, instead of adding extra information at the last moment.\n\n❌ So don't write:\n\n```go\nfunc a() error {\n    return b()\n}\n\nfunc b() error {\n    return c()\n}\n\nfunc c() error {\n    return d()\n}\n\nfunc d() error {\n    return oops.\n        Code(\"iam_missing_permission\").\n        In(\"authz\").\n        Trace(\"4ea76885-a371-46b0-8ce0-b72b277fa9af\").\n        Time(time.Now()).\n        With(\"hello\", \"world\").\n        With(\"permission\", \"post.create\").\n        Hint(\"Runbook: https://doc.acme.org/doc/abcd.md\").\n        User(\"user-123\", \"firstname\", \"john\", \"lastname\", \"doe\").\n        Tenant(\"organization-123\", \"name\", \"Microsoft\").\n        Errorf(\"permission denied\")\n}\n```\n\n✅ but write:\n\n```go\nfunc a() error {\n\treturn b()\n}\n\nfunc b() error {\n    return oops.\n        In(\"iam\").\n        Trace(\"4ea76885-a371-46b0-8ce0-b72b277fa9af\").\n        With(\"hello\", \"world\").\n        Wrapf(c(), \"something failed\")\n}\n\nfunc c() error {\n    return d()\n}\n\nfunc d() error {\n    return oops.\n        Code(\"iam_missing_permission\").\n        In(\"authz\").\n        Time(time.Now()).\n        With(\"permission\", \"post.create\").\n        Hint(\"Runbook: https://doc.acme.org/doc/abcd.md\").\n        User(\"user-123\", \"firstname\", \"john\", \"lastname\", \"doe\").\n        Tenant(\"organization-123\", \"name\", \"Microsoft\").\n        Errorf(\"permission denied\")\n}\n```\n\n## 🤝 Contributing\n\n- Ping me on Twitter [@samuelberthe](https://twitter.com/samuelberthe) (DMs, mentions, whatever :))\n- Fork the [project](https://github.com/samber/oops)\n- Fix [open issues](https://github.com/samber/oops/issues) or request new features\n\nDon't hesitate ;)\n\n```bash\n# Install some dev dependencies\nmake tools\n\n# Run tests\nmake test\n# or\nmake watch-test\n```\n\n## 👤 Contributors\n\n![Contributors](https://contrib.rocks/image?repo=samber/oops)\n\n## 💫 Show your support\n\nGive a ⭐️ if this project helped you!\n\n[![GitHub Sponsors](https://img.shields.io/github/sponsors/samber?style=for-the-badge)](https://github.com/sponsors/samber)\n\n## 📝 License\n\nCopyright © 2023 [Samuel Berthe](https://github.com/samber).\n\nThis project is [MIT](./LICENSE) licensed.\n","funding_links":["https://github.com/sponsors/samber"],"categories":["Error Handling","Go","错误处理"],"sub_categories":["Search and Analytic Databases","检索及分析资料库"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamber%2Foops","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsamber%2Foops","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamber%2Foops/lists"}