-
Notifications
You must be signed in to change notification settings - Fork 213
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ref: Reworked net/http integration, wrote better example and complete…
… readme
- Loading branch information
1 parent
2719e83
commit f6d9f2a
Showing
6 changed files
with
202 additions
and
124 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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://[email protected]/1337", | ||
Transport: new(devNullTransport), | ||
Integrations: func(i []sentry.Integration) []sentry.Integration { | ||
return append(i, new(extractUser)) | ||
_ = sentry.Init(sentry.ClientOptions{ | ||
Dsn: "https://[email protected]/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) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
<p align="center"> | ||
<a href="https://sentry.io" target="_blank" align="center"> | ||
<img src="https://sentry-brand.storage.googleapis.com/sentry-logo-black.png" width="280"> | ||
</a> | ||
<br /> | ||
</p> | ||
|
||
# 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 | ||
}, | ||
}) | ||
``` |
Oops, something went wrong.