Skip to content

Commit

Permalink
ref: Reworked net/http integration, wrote better example and complete…
Browse files Browse the repository at this point in the history
… readme
  • Loading branch information
kamilogorek committed Jun 12, 2019
1 parent 2719e83 commit f6d9f2a
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 124 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
136 changes: 33 additions & 103 deletions example/http/main.go
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)
Expand Down
2 changes: 1 addition & 1 deletion gin/sentrygin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
135 changes: 135 additions & 0 deletions http/README.md
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
},
})
```
Loading

0 comments on commit f6d9f2a

Please sign in to comment.