Skip to content

Commit a084ac9

Browse files
ribicearsmnvaind
authored
Fiber integration (#795)
* feat: Fiber integration * docs: Add Fiber readme * misc: Add Fiber example * misc: Remove unused package * fix: some issues to pass tests * Compatible with Fiber v2 * Revert "Compatible with Fiber v2" This reverts commit f4f7c8e. * Upgrade Fiber to v2 * goimports files * move example to to correct directory * move example to to correct directory * go mod tidy * Update fiber/sentryfiber.go Co-authored-by: Ivan Dlugos <[email protected]> * remove cookie copy (already copied as a header) * Update fiber/sentryfiber_test.go Co-authored-by: Ivan Dlugos <[email protected]> * update changelog --------- Co-authored-by: arsmn <[email protected]> Co-authored-by: Ivan Dlugos <[email protected]> Co-authored-by: Ivan Dlugos <[email protected]>
1 parent 71c244b commit a084ac9

File tree

9 files changed

+594
-39
lines changed

9 files changed

+594
-39
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- Accept `interface{}` for span data values ([#784](https://github.com/getsentry/sentry-go/pull/784))
88
- Automatic transactions for Echo integration ([#722](https://github.com/getsentry/sentry-go/pull/722))
99
- Automatic transactions for Fasthttp integration ([#732](https://github.com/getsentry/sentry-go/pull/723))
10+
- Add `Fiber` integration ([#795](https://github.com/getsentry/sentry-go/pull/795))
1011

1112
## 0.27.0
1213

_examples/fiber/main.go

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/getsentry/sentry-go"
7+
sentryfiber "github.com/getsentry/sentry-go/fiber"
8+
"github.com/gofiber/fiber/v2"
9+
"github.com/gofiber/fiber/v2/utils"
10+
)
11+
12+
func main() {
13+
_ = sentry.Init(sentry.ClientOptions{
14+
Dsn: "",
15+
BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
16+
if hint.Context != nil {
17+
if ctx, ok := hint.Context.Value(sentry.RequestContextKey).(*fiber.Ctx); ok {
18+
// You have access to the original Context if it panicked
19+
fmt.Println(utils.CopyString(ctx.Hostname()))
20+
}
21+
}
22+
fmt.Println(event)
23+
return event
24+
},
25+
Debug: true,
26+
AttachStacktrace: true,
27+
})
28+
29+
// Later in the code
30+
sentryHandler := sentryfiber.New(sentryfiber.Options{
31+
Repanic: true,
32+
WaitForDelivery: true,
33+
})
34+
35+
enhanceSentryEvent := func(ctx *fiber.Ctx) error {
36+
if hub := sentryfiber.GetHubFromContext(ctx); hub != nil {
37+
hub.Scope().SetTag("someRandomTag", "maybeYouNeedIt")
38+
}
39+
return ctx.Next()
40+
}
41+
42+
app := fiber.New()
43+
44+
app.Use(sentryHandler)
45+
46+
app.All("/foo", enhanceSentryEvent, func(c *fiber.Ctx) error {
47+
panic("y tho")
48+
})
49+
50+
app.All("/", func(ctx *fiber.Ctx) error {
51+
if hub := sentryfiber.GetHubFromContext(ctx); hub != nil {
52+
hub.WithScope(func(scope *sentry.Scope) {
53+
scope.SetExtra("unwantedQuery", "someQueryDataMaybe")
54+
hub.CaptureMessage("User provided unwanted query string, but we recovered just fine")
55+
})
56+
}
57+
return ctx.SendStatus(fiber.StatusOK)
58+
})
59+
60+
if err := app.Listen(":3000"); err != nil {
61+
panic(err)
62+
}
63+
}

fiber/README.md

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<p align="center">
2+
<a href="https://sentry.io" target="_blank" align="center">
3+
<img src="https://sentry-brand.storage.googleapis.com/sentry-logo-black.png" width="280">
4+
</a>
5+
<br />
6+
</p>
7+
8+
# Official Sentry fiber Handler for Sentry-go SDK
9+
10+
**Godoc:** https://godoc.org/github.com/getsentry/sentry-go/fiber
11+
12+
**Example:** https://github.com/getsentry/sentry-go/tree/master/example/fiber
13+
14+
## Installation
15+
16+
```sh
17+
go get github.com/getsentry/sentry-go/fiber
18+
```
19+
20+
```go
21+
import (
22+
"fmt"
23+
"github.com/gofiber/fiber/v2"
24+
"github.com/getsentry/sentry-go"
25+
sentryfiber "github.com/getsentry/sentry-go/fiber"
26+
"github.com/gofiber/fiber/v2/utils"
27+
)
28+
```
29+
30+
To initialize Sentry's handler, you need to initialize Sentry itself beforehand
31+
32+
```go
33+
if err := sentry.Init(sentry.ClientOptions{
34+
Dsn: "your-public-dsn",
35+
}); err != nil {
36+
fmt.Printf("Sentry initialization failed: %v\n", err)
37+
}
38+
39+
// Create an instance of sentryfiber
40+
sentryHandler := sentryfiber.New(sentryfiber.Options{})
41+
42+
// Once it's done, you can attach the handler as one of your middlewares
43+
app := fiber.New()
44+
45+
app.Use(sentryHandler)
46+
47+
// And run it
48+
app.Listen(":3000")
49+
```
50+
51+
## Configuration
52+
53+
`sentryfiber` accepts a struct of `Options` that allows you to configure how the handler will behave.
54+
55+
Currently it respects 3 options:
56+
57+
```go
58+
// Repanic configures whether Sentry should repanic after recovery, in most cases it should be set to false,
59+
// as fasthttp doesn't include it's own Recovery handler.
60+
Repanic bool
61+
// WaitForDelivery configures whether you want to block the request before moving forward with the response.
62+
// Because fasthttp doesn't include it's own `Recovery` handler, it will restart the application,
63+
// and event won't be delivered otherwise.
64+
WaitForDelivery bool
65+
// Timeout for the event delivery requests.
66+
Timeout time.Duration
67+
```
68+
69+
## Usage
70+
71+
`sentryfiber` attaches an instance of `*sentry.Hub` (https://godoc.org/github.com/getsentry/sentry-go#Hub) to the request's context, which makes it available throughout the rest of the request's lifetime.
72+
You can access it by using the `sentryfiber.GetHubFromContext()` method on the context itself in any of your proceeding middleware and routes.
73+
And it should be used instead of the global `sentry.CaptureMessage`, `sentry.CaptureException`, or any other calls, as it keeps the separation of data between the requests.
74+
75+
**Keep in mind that `*sentry.Hub` won't be available in middleware attached before to `sentryfiber`!**
76+
77+
```go
78+
// Later in the code
79+
sentryHandler := sentryfiber.New(sentryfiber.Options{
80+
Repanic: true,
81+
WaitForDelivery: true,
82+
})
83+
84+
enhanceSentryEvent := func(ctx *fiber.Ctx) {
85+
if hub := sentryfiber.GetHubFromContext(ctx); hub != nil {
86+
hub.Scope().SetTag("someRandomTag", "maybeYouNeedIt")
87+
}
88+
ctx.Next()
89+
}
90+
91+
app := fiber.New()
92+
93+
app.Use(sentryHandler)
94+
95+
app.All("/foo", enhanceSentryEvent, func(ctx *fiber.Ctx) {
96+
panic("y tho")
97+
})
98+
99+
app.All("/", func(ctx *fiber.Ctx) {
100+
if hub := sentryfiber.GetHubFromContext(ctx); hub != nil {
101+
hub.WithScope(func(scope *sentry.Scope) {
102+
scope.SetExtra("unwantedQuery", "someQueryDataMaybe")
103+
hub.CaptureMessage("User provided unwanted query string, but we recovered just fine")
104+
})
105+
}
106+
ctx.Status(fiber.StatusOK)
107+
})
108+
109+
app.Listen(":3000")
110+
```
111+
112+
### Accessing Context in `BeforeSend` callback
113+
114+
```go
115+
sentry.Init(sentry.ClientOptions{
116+
Dsn: "your-public-dsn",
117+
BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
118+
if hint.Context != nil {
119+
if ctx, ok := hint.Context.Value(sentry.RequestContextKey).(*fiber.Ctx); ok {
120+
// You have access to the original Context if it panicked
121+
fmt.Println(ctx.Hostname())
122+
}
123+
}
124+
return event
125+
},
126+
})
127+
```

fiber/sentryfiber.go

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package sentryfiber
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
"net/url"
10+
"time"
11+
12+
"github.com/gofiber/fiber/v2"
13+
"github.com/gofiber/fiber/v2/utils"
14+
15+
"github.com/getsentry/sentry-go"
16+
)
17+
18+
const valuesKey = "sentry"
19+
20+
type handler struct {
21+
repanic bool
22+
waitForDelivery bool
23+
timeout time.Duration
24+
}
25+
26+
type Options struct {
27+
// Repanic configures whether Sentry should repanic after recovery, in most cases it should be set to false,
28+
// as fasthttp doesn't include it's own Recovery handler.
29+
Repanic bool
30+
// WaitForDelivery configures whether you want to block the request before moving forward with the response.
31+
// Because fasthttp doesn't include it's own Recovery handler, it will restart the application,
32+
// and event won't be delivered otherwise.
33+
WaitForDelivery bool
34+
// Timeout for the event delivery requests.
35+
Timeout time.Duration
36+
}
37+
38+
func New(options Options) fiber.Handler {
39+
handler := handler{
40+
repanic: options.Repanic,
41+
timeout: time.Second * 2,
42+
waitForDelivery: options.WaitForDelivery,
43+
}
44+
45+
if options.Timeout != 0 {
46+
handler.timeout = options.Timeout
47+
}
48+
49+
return handler.handle
50+
}
51+
52+
func (h *handler) handle(ctx *fiber.Ctx) error {
53+
hub := sentry.CurrentHub().Clone()
54+
scope := hub.Scope()
55+
56+
scope.SetRequest(convert(ctx))
57+
scope.SetRequestBody(ctx.Request().Body())
58+
ctx.Locals(valuesKey, hub)
59+
defer h.recoverWithSentry(hub, ctx)
60+
return ctx.Next()
61+
}
62+
63+
func (h *handler) recoverWithSentry(hub *sentry.Hub, ctx *fiber.Ctx) {
64+
if err := recover(); err != nil {
65+
eventID := hub.RecoverWithContext(
66+
context.WithValue(context.Background(), sentry.RequestContextKey, ctx),
67+
err,
68+
)
69+
if eventID != nil && h.waitForDelivery {
70+
hub.Flush(h.timeout)
71+
}
72+
if h.repanic {
73+
panic(err)
74+
}
75+
}
76+
}
77+
78+
func GetHubFromContext(ctx *fiber.Ctx) *sentry.Hub {
79+
if hub, ok := ctx.Locals(valuesKey).(*sentry.Hub); ok {
80+
return hub
81+
}
82+
return nil
83+
}
84+
85+
func convert(ctx *fiber.Ctx) *http.Request {
86+
defer func() {
87+
if err := recover(); err != nil {
88+
sentry.Logger.Printf("%v", err)
89+
}
90+
}()
91+
92+
r := new(http.Request)
93+
94+
r.Method = utils.CopyString(ctx.Method())
95+
uri := ctx.Request().URI()
96+
r.URL, _ = url.Parse(fmt.Sprintf("%s://%s%s", uri.Scheme(), uri.Host(), uri.Path()))
97+
98+
// Headers
99+
r.Header = make(http.Header)
100+
ctx.Request().Header.VisitAll(func(key, value []byte) {
101+
r.Header.Add(string(key), string(value))
102+
})
103+
r.Host = utils.CopyString(ctx.Hostname())
104+
105+
// Env
106+
r.RemoteAddr = ctx.Context().RemoteAddr().String()
107+
108+
// QueryString
109+
r.URL.RawQuery = string(ctx.Request().URI().QueryString())
110+
111+
// Body
112+
r.Body = io.NopCloser(bytes.NewReader(ctx.Request().Body()))
113+
114+
return r
115+
}

0 commit comments

Comments
 (0)