Skip to content

Commit

Permalink
merge
Browse files Browse the repository at this point in the history
  • Loading branch information
samber committed Oct 31, 2023
2 parents 55ca66c + a8fa380 commit 7c9e7a1
Show file tree
Hide file tree
Showing 12 changed files with 197 additions and 68 deletions.
28 changes: 2 additions & 26 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ name: Lint

on:
push:
tags:
branches:
pull_request:

jobs:
Expand All @@ -17,28 +15,6 @@ jobs:
stable: false
- uses: actions/checkout@v2
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
uses: golangci/golangci-lint-action@v3
with:
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
version: latest

# Optional: working directory, useful for monorepos
working-directory: ./

# Optional: golangci-lint command line arguments.
args: --timeout 60s --max-same-issues 50

# Optional: show only new issues if it's a pull request. The default value is `false`.
# only-new-issues: true

# Optional: if set to true then the action will use pre-installed Go.
# skip-go-installation: true

# Optional: if set to true then the action don't cache or restore ~/go/pkg.
# skip-pkg-cache: true

# Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
# skip-build-cache: true

# optionally use a specific version of Go rather than the latest one
go_version: '1.21.3'
args: --timeout 120s --max-same-issues 50
37 changes: 20 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,23 +220,24 @@ The library provides an error builder. Each method can be used standalone (eg: `

The `oops.OopsError` builder must finish with either `.Errorf(...)`, `.Wrap(...)` or `.Wrapf(...)`.

| Builder method | Getter | Description |
| --------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `.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. |
| `.Code(string)` | `err.Code() string` | Set a code or slug that describes the error. Error messages are intented to be read by humans, but such code is expected to be read by machines and be transported over different services |
| `.Time(time.Time)` | `err.Time() time.Time` | Set the error time (default: `time.Now()`) |
| `.Since(time.Time)` | `err.Duration() time.Duration` | Set the error duration |
| `.Duration(time.Duration)` | `err.Duration() time.Duration` | Set the error duration |
| `.In(string)` | `err.Domain() string` | Set the feature category or domain |
| `.Tags(...string)` | `err.Tags() []string` | Add multiple tags, describing the feature returning an error |
| `.Trace(string)` | `err.Trace() string` | Add a transaction id, trace id, correlation id... (default: ULID) |
| `.Span(string)` | `err.Span() string` | Add a span representing a unit of work or operation... (default: ULID) |
| `.Hint(string)` | `err.Hint() string` | Set a hint for faster debugging |
| `.Owner(string)` | `err.Owner() (string)` | Set the name/email of the collegue/team responsible for handling this error. Useful for alerting purpose |
| `.User(string, any...)` | `err.User() (string, map[string]any)` | Supply user id and a chain of key/value |
| `.Tenant(string, any...)` | `err.Tenant() (string, map[string]any)` | Supply tenant id and a chain of key/value |
| `.Request(*http.Request, bool)` | `err.Request() *http.Request` | Supply http request |
| `.Response(*http.Response, bool)` | `err.Response() *http.Response` | Supply http response |
| Builder method | Getter | Description |
| --------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `.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. |
| `.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. |
| `.Code(string)` | `err.Code() string` | Set a code or slug that describes the error. Error messages are intented to be read by humans, but such code is expected to be read by machines and be transported over different services |
| `.Time(time.Time)` | `err.Time() time.Time` | Set the error time (default: `time.Now()`) |
| `.Since(time.Time)` | `err.Duration() time.Duration` | Set the error duration |
| `.Duration(time.Duration)` | `err.Duration() time.Duration` | Set the error duration |
| `.In(string)` | `err.Domain() string` | Set the feature category or domain |
| `.Tags(...string)` | `err.Tags() []string` | Add multiple tags, describing the feature returning an error |
| `.Trace(string)` | `err.Trace() string` | Add a transaction id, trace id, correlation id... (default: ULID) |
| `.Span(string)` | `err.Span() string` | Add a span representing a unit of work or operation... (default: ULID) |
| `.Hint(string)` | `err.Hint() string` | Set a hint for faster debugging |
| `.Owner(string)` | `err.Owner() (string)` | Set the name/email of the collegue/team responsible for handling this error. Useful for alerting purpose |
| `.User(string, any...)` | `err.User() (string, map[string]any)` | Supply user id and a chain of key/value |
| `.Tenant(string, any...)` | `err.Tenant() (string, map[string]any)` | Supply tenant id and a chain of key/value |
| `.Request(*http.Request, bool)` | `err.Request() *http.Request` | Supply http request |
| `.Response(*http.Response, bool)` | `err.Response() *http.Response` | Supply http response |

#### Examples

Expand All @@ -251,11 +252,13 @@ err2 := oops.
Errorf("could not fetch user")

// with custom attributes
ctx := context.WithContext(context.Background(), "a key", "value")
err3 := oops.
With("driver", "postgresql").
With("query", query).
With("query.duration", queryDuration).
With("lorem", func() string { return "ipsum" }). // lazy evaluation
WithContext(ctx, "a key", "another key").
Errorf("could not fetch user")

// with trace+span
Expand Down
46 changes: 22 additions & 24 deletions builder.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package oops

import (
"context"
"fmt"
"net/http"
"time"
Expand Down Expand Up @@ -147,12 +148,6 @@ func (o OopsErrorBuilder) Recover(cb func()) (err error) {
} else {
err = o.Wrap(fmt.Errorf("%v", r))
}

// without this, the stacktrace would have start to the Wrap() call
e := err.(OopsError)
if len(e.stacktrace.frames) > 0 { // just for safety, should always be true
e.stacktrace.frames = e.stacktrace.frames[1:]
}
}
}()

Expand All @@ -162,24 +157,7 @@ func (o OopsErrorBuilder) Recover(cb func()) (err error) {

// Recoverf handle panic and returns `oops.OopsError` object that satisfies `error` and formats an error message.
func (o OopsErrorBuilder) Recoverf(cb func(), msg string, args ...any) (err error) {
defer func() {
if r := recover(); r != nil {
if e, ok := r.(error); ok {
err = o.Wrapf(e, msg, args...)
} else {
err = o.Wrapf(o.Errorf("%v", r), msg, args...)
}

// without this, the stacktrace would have start to the Wrapf() call
e := err.(OopsError)
if len(e.stacktrace.frames) > 0 { // just for safety, should always be true
e.stacktrace.frames = e.stacktrace.frames[1:]
}
}
}()

cb()
return
return o.Wrapf(o.Recover(cb), msg, args...)
}

// Assert panics if condition is false. Panic payload will be of type oops.OopsError.
Expand Down Expand Up @@ -262,6 +240,26 @@ func (o OopsErrorBuilder) With(kv ...any) OopsErrorBuilder {
return o2
}

// WithContext supplies a list of values declared in context.
func (o OopsErrorBuilder) WithContext(ctx context.Context, keys ...any) OopsErrorBuilder {
o2 := o.copy()

for i := 0; i < len(keys); i++ {
switch k := keys[i].(type) {
case fmt.Stringer:
o2.context[k.String()] = contextValueOrNil(ctx, k.String())
case string:
o2.context[k] = contextValueOrNil(ctx, k)
case *string:
o2.context[*k] = contextValueOrNil(ctx, *k)
default:
o2.context[fmt.Sprint(k)] = contextValueOrNil(ctx, k)
}
}

return o2
}

// Trace set a transaction id, trace id or correlation id...
func (o OopsErrorBuilder) Trace(trace string) OopsErrorBuilder {
o2 := o.copy()
Expand Down
2 changes: 2 additions & 0 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (

var SourceFragmentsHidden = true

var _ error = (*OopsError)(nil)

type OopsError struct {
err error
msg string
Expand Down
9 changes: 9 additions & 0 deletions examples/segfault/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

# Example of segfault handling

Playground: https://go.dev/play/p/66wkzJ-Rem1

```sh
go run examples/segfault/example.go 2>&1 | jq
go run examples/segfault/example.go 2>&1 | jq .stacktrace -r
```
40 changes: 40 additions & 0 deletions examples/segfault/example.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package main

import (
"github.com/samber/oops"
oopslogrus "github.com/samber/oops/loggers/logrus"
"github.com/sirupsen/logrus"
)

// go run examples/segfault/example.go 2>&1 | jq
// go run examples/segfault/example.go 2>&1 | jq .stacktrace -r

func nilPointerException() {
var a *int
*a = 42
}

func handlePanic() error {
return oops.
Code("iam_authz_missing_permission").
In("authz").
With("permission", "post.create").
Trace("6710668a-2b2a-4de6-b8cf-3272a476a1c9").
Hint("Runbook: https://doc.acme.org/doc/abcd.md").
Recoverf(func() {
// ...
nilPointerException()
// ...
}, "unexpected error")
}

func main() {
logrus.SetFormatter(oopslogrus.NewOopsFormatter(&logrus.JSONFormatter{
PrettyPrint: true,
}))

err := handlePanic()
if err != nil {
logrus.WithError(err).Error(err)
}
}
15 changes: 15 additions & 0 deletions examples/segfault/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module github.com/samber/oops/examples/segfault

go 1.21

require (
github.com/stretchr/testify v1.8.2
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/samber/lo v1.38.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
25 changes: 25 additions & 0 deletions examples/segfault/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/samber/slog-formatter v0.3.3 h1:VCoKANbPtXf00CtnvKn/BZ7gcH1dBBnm48PAH854ynQ=
github.com/samber/slog-formatter v0.3.3/go.mod h1:C8LO3jmgtpSAxw7pm9xLjPcJ/h4qzw3OfVIMASyEKQ0=
github.com/samber/slog-multi v0.4.0 h1:QTQAo+9AP295irccqKdNwJ/2XflRMuL/aHqk7RblOhE=
github.com/samber/slog-multi v0.4.0/go.mod h1:QDicB1R5oTcbSSqlYmskphC7fNcjHgdxqZdns1eAvDc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o=
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
1 change: 1 addition & 0 deletions go.work
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ use (
./examples/logrus
./examples/sources
./examples/panic
./examples/segfault
)
6 changes: 6 additions & 0 deletions oops.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package oops

import (
"context"
"net/http"
"time"
)
Expand Down Expand Up @@ -100,6 +101,11 @@ func With(kv ...any) OopsErrorBuilder {
return new().With(kv...)
}

// With supplies a list of attributes declared by pair of key+value.
func WithContext(ctx context.Context, keys ...any) OopsErrorBuilder {
return new().WithContext(ctx, keys...)
}

// Hint set a hint for faster debugging.
func Hint(hint string) OopsErrorBuilder {
return new().Hint(hint)
Expand Down
Loading

0 comments on commit 7c9e7a1

Please sign in to comment.