Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

doc: Revamp sentryhttp documentation #304

Merged
merged 1 commit into from
Nov 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 5 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,13 @@ issues:
linters:
- prealloc
- path: _test\.go
test: "G306:"
text: "G306:"
linters:
- gosec
- path: errors_test\.go
linters:
- unused
- path: http/example_test\.go
linters:
- errcheck
- bodyclose
193 changes: 149 additions & 44 deletions example/http/main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
// This is an example web server to demonstrate how to instrument web servers
// with Sentry.
//
// Try it by running:
//
// go run main.go
//
// To actually report events to Sentry, set the DSN either by editing the
// appropriate line below or setting the environment variable SENTRY_DSN to
// match the DSN of your Sentry project.
package main

import (
"context"
"crypto/sha256"
"flag"
"fmt"
"html/template"
"image"
"image/png"
"log"
"net/http"
"time"
Expand All @@ -10,67 +26,156 @@ import (
sentryhttp "github.com/getsentry/sentry-go/http"
)

type handler struct{}
var addr = flag.String("addr", "127.0.0.1:3000", "bind address")

func (h *handler) ServeHTTP(w 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")
})
}
}

func enhanceSentryEvent(handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if hub := sentry.GetHubFromContext(r.Context()); hub != nil {
hub.Scope().SetTag("someRandomTag", "maybeYouNeedIt")
}
handler(w, r)
}
func main() {
flag.Parse()
configureLoggers()
// The helper run function does not call log.Fatal, otherwise deferred
// function calls would not be executed when the program exits.
log.Fatal(run())
}

// run runs a web server. As with http.ListenAndServe, the returned error is
// always non-nil.
func run() error {
err := sentry.Init(sentry.ClientOptions{
// Either set your DSN here or set the SENTRY_DSN environment variable.
Dsn: "",
// Enable printing of SDK debug messages.
// Useful when getting started or trying to figure something out.
Debug: true,
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)
// Here you can inspect/modify events before they are sent.
// Returning nil drops the event.
log.Printf("BeforeSend event [%s]", event.EventID)
return event
},
Debug: true,
AttachStacktrace: true,
})
if err != nil {
return err
}
// Flush buffered events before the program terminates.
// Set the timeout to the maximum duration the program can afford to wait.
defer sentry.Flush(2 * time.Second)

sentryHandler := sentryhttp.New(sentryhttp.Options{
Repanic: true,
// Main HTTP handler, renders an HTML page with a random image.
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
// Use GetHubFromContext to get a hub associated with the current
// request. Hubs provide data isolation, such that tags, breadcrumbs
// and other attributes are never mixed up across requests.
hub := sentry.GetHubFromContext(r.Context())
hub.Scope().SetTag("url", r.URL.Path)
hub.CaptureMessage("Page Not Found")
http.NotFound(w, r)
return
}

err := t.Execute(w, time.Now().UnixNano())
if err != nil {
log.Printf("[%s] %s", r.URL.Path, err)
return
}
})

// HTTP handler for the random image.
http.HandleFunc("/random.png", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var cancel context.CancelFunc
if timeout, err := time.ParseDuration(r.URL.Query().Get("timeout")); err == nil {
log.Printf("Rendering random image with timeout = %v", timeout)
ctx, cancel = context.WithTimeout(ctx, timeout)
defer cancel()
}

q := r.URL.Query().Get("q")
img := NewImage(ctx, 128, 128, []byte(q))

err := png.Encode(w, img)
if err != nil {
log.Printf("[%s] %s", r.URL.Path, err)
hub := sentry.GetHubFromContext(ctx)
hub.CaptureException(err)
code := http.StatusInternalServerError
http.Error(w, http.StatusText(code), code)
return
}
})

http.HandleFunc("/panic/", func(w http.ResponseWriter, r *http.Request) {
var s []int
fmt.Fprint(w, s[42]) // this line will panic
})

http.Handle("/", sentryHandler.Handle(&handler{}))
http.Handle("/foo", sentryHandler.Handle(
enhanceSentryEvent(func(w http.ResponseWriter, r *http.Request) {
panic("y tho")
}),
))
http.Handle("/bar", sentryHandler.HandleFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "ok")
}))

log.Print("Listening and serving HTTP on :3000")
return http.ListenAndServe(":3000", nil)
log.Printf("Serving http://%s", *addr)

// Wrap the default mux with Sentry to capture panics and report errors.
//
// Alternatively, you can also wrap individual handlers if you need to use
// different options for different parts of your app.
handler := sentryhttp.New(sentryhttp.Options{}).Handle(http.DefaultServeMux)
return http.ListenAndServe(*addr, handler)
}

func main() {
// The run function itself does not call log.Fatal, otherwise deferred
// function calls would not be executed when the program exits.
log.Fatal(run())
var t = template.Must(template.New("").Parse(`<!DOCTYPE html>
<html lang="en">
<head>
<title>Random Image</title>
<style>
img {
width: 128px;
height: 128px;
}
</style>
<base target="_blank">
</head>
<body>
<h1>Random Image</h1>
<img src="/random.png?q={{.}}" alt="Random Image">
<h2>Click one of these links to send an event to Sentry</h2>
<ul>
<li><a href="/random.png?q={{.}}&timeout=20ms">Open random image and abort if it takes longer than 20ms</a></li>
<li><a href="/404">Trigger 404 not found error</a></li>
<li><a href="/panic/">Trigger server-side panic</a></li>
</ul>
</body>
</html>`))

// NewImage returns a random image based on seed, with the given width and
// height.
func NewImage(ctx context.Context, width, height int, seed []byte) image.Image {
b := sha256.Sum256(seed)

img := image.NewGray(image.Rect(0, 0, width, height))

for i := 0; i < len(img.Pix); i += len(b) {
select {
case <-ctx.Done():
// Context canceled, abort image generation.
// Spot the bug: the returned image cannot be encoded as PNG and
// will cause an error that will be reported to Sentry.
return img.SubImage(image.Rect(0, 0, 0, 0))
default:
}
p := b[:]
for j := 0; j < i; j++ {
tmp := sha256.Sum256(p)
p = tmp[:]
}
copy(img.Pix[i:], p)
}
return img
}

// configureLoggers configures the standard logger and the logger used by the
// Sentry SDK.
//
// The only reason to change logger configuration in this example is aesthetics.
func configureLoggers() {
logFlags := log.Ldate | log.Ltime
sentry.Logger.SetPrefix("[sentry sdk] ")
sentry.Logger.SetFlags(logFlags)
log.SetPrefix("[http example] ")
log.SetFlags(logFlags)
}
33 changes: 33 additions & 0 deletions http/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package sentryhttp_test

import (
"net/http"

"github.com/getsentry/sentry-go"
sentryhttp "github.com/getsentry/sentry-go/http"
)

// For a longer and executable example, see
// https://github.com/getsentry/sentry-go/tree/master/example/http.
func Example() {
// Initialize the Sentry SDK once in the main function.
// sentry.Init(...)

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// Use GetHubFromContext to get a hub associated with the current
// request. Hubs provide data isolation, such that tags, breadcrumbs
// and other attributes are never mixed up across requests.
hub := sentry.GetHubFromContext(r.Context())
_, err := http.Get("example.com")
if err != nil {
hub.CaptureException(err)
}
})

// Wrap the default mux with Sentry to capture panics and report errors.
//
// Alternatively, you can also wrap individual handlers if you need to use
// different options for different parts of your app.
handler := sentryhttp.New(sentryhttp.Options{}).Handle(http.DefaultServeMux)
http.ListenAndServe(":0", handler)
}
24 changes: 20 additions & 4 deletions http/sentryhttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,28 @@ type Handler struct {

// Options configure a Handler.
type Options struct {
// Repanic configures whether Sentry should repanic after recovery
// Repanic configures whether to panic again after recovering from a panic.
// Use this option if you have other panic handlers or want the default
// behavior from Go's http package, as documented in
// https://golang.org/pkg/net/http/#Handler.
Repanic bool
// WaitForDelivery indicates whether to wait until panic details have been
// sent to Sentry before panicking or proceeding with a request.
// WaitForDelivery indicates, in case of a panic, whether to block the
// current goroutine and wait until the panic event has been reported to
// Sentry before repanicking or resuming normal execution.
//
// This option is normally not needed. Unless you need different behaviors
// for different HTTP handlers, configure the SDK to use the
// HTTPSyncTransport instead.
//
// Waiting (or using HTTPSyncTransport) is useful when the web server runs
// in an environment that interrupts execution at the end of a request flow,
// like modern serverless platforms.
WaitForDelivery bool
// Timeout for the event delivery requests.
// Timeout for the delivery of panic events. Defaults to 2s. Only relevant
// when WaitForDelivery is true.
//
// If the timeout is reached, the current goroutine is no longer blocked
// waiting, but the delivery is not canceled.
Timeout time.Duration
}

Expand Down