diff --git a/CHANGELOG.md b/CHANGELOG.md
index d58e46161..b5e8cba57 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
## 0.0.1-beta.4
- feat: `IgnoreErrors` client option and corresponding integration
+- ref: Reworked `net/http` integration, wrote better example and complete readme
- ref: Reworked `Gin` integration, wrote better example and complete readme
- ref: Reworked `Iris` integration, wrote better example and complete readme
- ref: Reworked `Negroni` integration, wrote better example and complete readme
diff --git a/example/http/main.go b/example/http/main.go
index f402e0a5b..a86587d85 100644
--- a/example/http/main.go
+++ b/example/http/main.go
@@ -1,133 +1,63 @@
package main
import (
- "context"
- "encoding/json"
- "errors"
"fmt"
- "log"
"net/http"
- "strconv"
- "time"
"github.com/getsentry/sentry-go"
sentryhttp "github.com/getsentry/sentry-go/http"
)
-func prettyPrint(v interface{}) string {
- pp, _ := json.MarshalIndent(v, "", " ")
- return string(pp)
-}
-
-type ctxKey int
-
-const UserCtxKey = ctxKey(1337)
-
-type devNullTransport struct{}
-
-func (t *devNullTransport) Configure(options sentry.ClientOptions) {
- dsn, _ := sentry.NewDsn(options.Dsn)
- fmt.Println()
- fmt.Println("Store Endpoint:", dsn.StoreAPIURL())
- fmt.Println("Headers:", dsn.RequestHeaders())
- fmt.Println()
-}
-func (t *devNullTransport) SendEvent(event *sentry.Event) {
- fmt.Println("Faked Transport")
- log.Println(prettyPrint(event))
-}
-
-func (t *devNullTransport) Flush(timeout time.Duration) bool {
- return true
-}
-
-func customHandlerFunc(w http.ResponseWriter, r *http.Request) {
- if sentry.HasHubOnContext(r.Context()) {
- hub := sentry.GetHubFromContext(r.Context())
- hub.AddBreadcrumb(&sentry.Breadcrumb{Message: "BreadcrumbFunc #1 - " + strconv.Itoa(int(time.Now().Unix()))}, nil)
- hub.AddBreadcrumb(&sentry.Breadcrumb{Message: "BreadcrumbFunc #2 - " + strconv.Itoa(int(time.Now().Unix()))}, nil)
- }
-
- panic(errors.New("HTTPPanicHandler Error"))
-}
-
-type User struct {
- id int
- name string
-}
+type handler struct{}
-func attachUser(handler http.HandlerFunc) http.HandlerFunc {
- return func(response http.ResponseWriter, request *http.Request) {
- ctx := request.Context()
- ctx = context.WithValue(ctx, UserCtxKey, User{
- id: 42,
- name: "PickleRick",
+func (h *handler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
+ if hub := sentry.GetHubFromContext(r.Context()); hub != nil {
+ hub.WithScope(func(scope *sentry.Scope) {
+ scope.SetExtra("unwantedQuery", "someQueryDataMaybe")
+ hub.CaptureMessage("User provided unwanted query string, but we recovered just fine")
})
- handler(response, request.WithContext(ctx))
}
+ rw.WriteHeader(http.StatusOK)
}
-type customHandler struct{}
-
-func (th *customHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- if sentry.HasHubOnContext(r.Context()) {
- hub := sentry.GetHubFromContext(r.Context())
- hub.AddBreadcrumb(&sentry.Breadcrumb{Message: "Breadcrumb #1 - " + strconv.Itoa(int(time.Now().Unix()))}, nil)
- hub.AddBreadcrumb(&sentry.Breadcrumb{Message: "Breadcrumb #2 - " + strconv.Itoa(int(time.Now().Unix()))}, nil)
- }
-
- sentry.CaptureMessage("CaptureMessage")
- sentry.CaptureException(errors.New("CaptureMessage"))
- panic("HTTPPanicHandler Message")
-}
-
-type extractUser struct{}
-
-func (eu extractUser) Name() string {
- return "extractUser"
-}
-
-func (eu extractUser) SetupOnce(client *sentry.Client) {
- client.AddEventProcessor(eu.processor)
-}
-
-func (eu extractUser) processor(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
- if hint != nil && hint.Context != nil {
- if u, ok := hint.Context.Value(UserCtxKey).(User); ok {
- event.User = sentry.User{
- ID: strconv.Itoa(u.id),
- Username: u.name,
- }
+func enhanceSentryEvent(handler http.HandlerFunc) http.HandlerFunc {
+ return func(rw http.ResponseWriter, r *http.Request) {
+ if hub := sentry.GetHubFromContext(r.Context()); hub != nil {
+ hub.Scope().SetTag("someRandomTag", "maybeYouNeedIt")
}
+ handler(rw, r)
}
-
- return event
}
func main() {
- err := sentry.Init(sentry.ClientOptions{
- Dsn: "https://hello@world.io/1337",
- Transport: new(devNullTransport),
- Integrations: func(i []sentry.Integration) []sentry.Integration {
- return append(i, new(extractUser))
+ _ = sentry.Init(sentry.ClientOptions{
+ Dsn: "https://363a337c11a64611be4845ad6e24f3ac@sentry.io/297378",
+ BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
+ if hint.Context != nil {
+ if req, ok := hint.Context.Value(sentry.RequestContextKey).(*http.Request); ok {
+ // You have access to the original Request
+ fmt.Println(req)
+ }
+ }
+ fmt.Println(event)
+ return event
},
+ Debug: true,
+ AttachStacktrace: true,
})
- if err != nil {
- panic(err)
- } else {
- fmt.Print("[Sentry] SDK initialized successfully\n\n")
- }
-
sentryHandler := sentryhttp.New(sentryhttp.Options{
- Repanic: true,
- WaitForDelivery: true,
+ Repanic: true,
})
- http.Handle("/handle", sentryHandler.Handle(&customHandler{}))
- http.HandleFunc("/handlefunc", attachUser(sentryHandler.HandleFunc(customHandlerFunc)))
+ http.Handle("/", sentryHandler.Handle(&handler{}))
+ http.HandleFunc("/foo", sentryHandler.HandleFunc(
+ enhanceSentryEvent(func(rw http.ResponseWriter, r *http.Request) {
+ panic("y tho")
+ }),
+ ))
- log.Println("Please call me at localhost:3000/handle or localhost:3000/handlefunc")
+ fmt.Println("Listening and serving HTTP on :3000")
if err := http.ListenAndServe(":3000", nil); err != nil {
panic(err)
diff --git a/gin/sentrygin.go b/gin/sentrygin.go
index 8ae9e0f37..2d8c1e959 100644
--- a/gin/sentrygin.go
+++ b/gin/sentrygin.go
@@ -72,7 +72,7 @@ func (h *handler) recoverWithSentry(hub *sentry.Hub, r *http.Request) {
}
}
-// GetHubFromContext retrieves attached *sentry.Hub instance from iris.Context
+// GetHubFromContext retrieves attached *sentry.Hub instance from iris.Context.
func GetHubFromContext(ctx *gin.Context) *sentry.Hub {
if hub, ok := ctx.Get(valuesKey); ok {
if hub, ok := hub.(*sentry.Hub); ok {
diff --git a/http/README.md b/http/README.md
new file mode 100644
index 000000000..905b8f048
--- /dev/null
+++ b/http/README.md
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+# Official Sentry net/http Handler for Sentry-go SDK
+
+**Godoc:** https://godoc.org/github.com/getsentry/sentry-go/http
+
+**Example:** https://github.com/getsentry/sentry-go/tree/master/example/http
+
+## Installation
+
+```sh
+go get github.com/getsentry/sentry-go/http
+```
+
+```go
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/getsentry/sentry-go"
+ sentryhttp "github.com/getsentry/sentry-go/http"
+)
+
+// In order to initialize Sentry's handler, you need to initialize Sentry itself beforehand
+if err := sentry.Init(sentry.ClientOptions{
+ Dsn: "your-public-dsn",
+}); err != nil {
+ fmt.Printf("Sentry initialization failed: %v\n", err)
+}
+
+// Create an instance of sentryhttp
+sentryHandler := sentryhttp.New(sentryhttp.Options{})
+
+// Once it's done, you can setup routes and attach the handler as one of your middlewares
+http.Handle("/", sentryHandler.Handle(&handler{}))
+http.HandleFunc("/foo", sentryHandler.HandleFunc(func(rw http.ResponseWriter, r *http.Request) {
+ panic("y tho")
+}))
+
+fmt.Println("Listening and serving HTTP on :3000")
+
+// And run it
+if err := http.ListenAndServe(":3000", nil); err != nil {
+ panic(err)
+}
+```
+
+## Configuration
+
+`sentryhttp` accepts a struct of `Options` that allows you to configure how the handler will behave.
+
+Currently it respects 3 options:
+
+```go
+// Whether Sentry should repanic after recovery, in most cases it should be set to true,
+// and you should gracefully handle http responses.
+Repanic bool
+// Whether you want to block the request before moving forward with the response.
+// Useful, when you want to restart the process after it panics.
+WaitForDelivery bool
+// Timeout for the event delivery requests.
+Timeout time.Duration
+```
+
+## Usage
+
+`sentryhttp` 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 request's lifetime.
+You can access it by using `sentry.GetHubFromContext()` method on the request itself in any of your proceeding middlewares and routes.
+And it should be used instead of global `sentry.CaptureMessage`, `sentry.CaptureException` or any other calls, as it keeps the separation of data between the requests.
+
+**Keep in mind that `*sentry.Hub` won't be available in middlewares attached prior to `sentryhttp`!**
+
+```go
+type handler struct{}
+
+func (h *handler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
+ if hub := sentry.GetHubFromContext(r.Context()); hub != nil {
+ hub.WithScope(func(scope *sentry.Scope) {
+ scope.SetExtra("unwantedQuery", "someQueryDataMaybe")
+ hub.CaptureMessage("User provided unwanted query string, but we recovered just fine")
+ })
+ }
+ rw.WriteHeader(http.StatusOK)
+}
+
+func enhanceSentryEvent(handler http.HandlerFunc) http.HandlerFunc {
+ return func(rw http.ResponseWriter, r *http.Request) {
+ if hub := sentry.GetHubFromContext(r.Context()); hub != nil {
+ hub.Scope().SetTag("someRandomTag", "maybeYouNeedIt")
+ }
+ handler(rw, r)
+ }
+}
+
+// Later in the code
+
+sentryHandler := sentryhttp.New(sentryhttp.Options{
+ Repanic: true,
+})
+
+http.Handle("/", sentryHandler.Handle(&handler{}))
+http.HandleFunc("/foo", sentryHandler.HandleFunc(
+ enhanceSentryEvent(func(rw http.ResponseWriter, r *http.Request) {
+ panic("y tho")
+ }),
+))
+
+fmt.Println("Listening and serving HTTP on :3000")
+
+if err := http.ListenAndServe(":3000", nil); err != nil {
+ panic(err)
+}
+```
+
+### Accessing Request in `BeforeSend` callback
+
+```go
+sentry.Init(sentry.ClientOptions{
+ Dsn: "your-public-dsn",
+ BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
+ if hint.Context != nil {
+ if req, ok := hint.Context.Value(sentry.RequestContextKey).(*http.Request); ok {
+ // You have access to the orihttpal Request here
+ }
+ }
+
+ return event
+ },
+})
+```
diff --git a/http/sentryhttp.go b/http/sentryhttp.go
index af8627c2d..71a6568fa 100644
--- a/http/sentryhttp.go
+++ b/http/sentryhttp.go
@@ -8,20 +8,28 @@ import (
"github.com/getsentry/sentry-go"
)
-type Handler struct {
+type handler struct {
repanic bool
waitForDelivery bool
timeout time.Duration
}
type Options struct {
- Repanic bool
+ // Repanic configures whether Sentry should repanic after recovery, in most cases it should be set to true,
+ // as iris.Default includes it's own Recovery middleware what handles http responses.
+ Repanic bool
+ // WaitForDelivery configures whether you want to block the request before moving forward with the response.
+ // Because Iris's default `Recovery` handler doesn't restart the application,
+ // it's safe to either skip this option or set it to `false`.
WaitForDelivery bool
- Timeout time.Duration
+ // Timeout for the event delivery requests.
+ Timeout time.Duration
}
-func New(options Options) *Handler {
- handler := Handler{
+// New returns a struct that provides Handle and HandleFunc methods
+// that satisfy http.Handler and http.HandlerFunc interfaces.
+func New(options Options) *handler {
+ handler := handler{
repanic: false,
timeout: time.Second * 2,
waitForDelivery: false,
@@ -38,35 +46,39 @@ func New(options Options) *Handler {
return &handler
}
-func (h *Handler) Handle(handler http.Handler) http.Handler {
+// Handle wraps http.Handler and recovers from caught panics.
+func (h *handler) Handle(handler http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
+ hub := sentry.CurrentHub().Clone()
ctx := sentry.SetHubOnContext(
- context.WithValue(r.Context(), sentry.RequestContextKey, r),
- sentry.CurrentHub().Clone(),
+ r.Context(),
+ hub,
)
- defer h.recoverWithSentry(ctx, r)
+ defer h.recoverWithSentry(hub, r)
handler.ServeHTTP(rw, r.WithContext(ctx))
})
}
-func (h *Handler) HandleFunc(handler http.HandlerFunc) http.HandlerFunc {
+// HandleFunc wraps http.HandleFunc and recovers from caught panics.
+func (h *handler) HandleFunc(handler http.HandlerFunc) http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
+ hub := sentry.CurrentHub().Clone()
ctx := sentry.SetHubOnContext(
- context.WithValue(r.Context(), sentry.RequestContextKey, r),
- sentry.CurrentHub().Clone(),
+ r.Context(),
+ hub,
)
- defer h.recoverWithSentry(ctx, r)
+ defer h.recoverWithSentry(hub, r)
handler(rw, r.WithContext(ctx))
}
}
-func (h *Handler) recoverWithSentry(ctx context.Context, r *http.Request) {
+func (h *handler) recoverWithSentry(hub *sentry.Hub, r *http.Request) {
if err := recover(); err != nil {
- hub := sentry.GetHubFromContext(ctx)
- hub.ConfigureScope(func(scope *sentry.Scope) {
- scope.SetRequest(sentry.Request{}.FromHTTPRequest(r))
- })
- eventID := hub.RecoverWithContext(ctx, err)
+ hub.Scope().SetRequest(sentry.Request{}.FromHTTPRequest(r))
+ eventID := hub.RecoverWithContext(
+ context.WithValue(r.Context(), sentry.RequestContextKey, r),
+ err,
+ )
if eventID != nil && h.waitForDelivery {
hub.Flush(h.timeout)
}
diff --git a/iris/sentryiris.go b/iris/sentryiris.go
index 89463102f..47e0270ab 100644
--- a/iris/sentryiris.go
+++ b/iris/sentryiris.go
@@ -72,7 +72,7 @@ func (h *handler) recoverWithSentry(hub *sentry.Hub, r *http.Request) {
}
}
-// GetHubFromContext retrieves attached *sentry.Hub instance from iris.Context
+// GetHubFromContext retrieves attached *sentry.Hub instance from iris.Context.
func GetHubFromContext(ctx iris.Context) *sentry.Hub {
if hub, ok := ctx.Values().Get(valuesKey).(*sentry.Hub); ok {
return hub