Logging Go Errors to Sentry

David Cramer /

So you’re picking up Go and wondering “where did all my exceptions go?” It takes a bit to wrap your head around using Go, especially if you’re coming from an interpreted language. Go error tracking is a lot different from Python error tracking or Ruby error tracking.

Handling Errors

If you’ve sought guidance on managing bugs in Go, you’ve probably ended up with a lot of your code looking like this:

f, err := os.Open("filename.ext")
if err != nil {
  log.Fatal(err)
}

If you’re doing this, don’t fret – it’s a straightforward approach to handling Go exceptions. The problem is that you’re doing it via traditional logging. Even worse — this won’t save you when your code hits a panic.

Addressing Panics

One of the big adjustments coming from other languages to Go is that standard exceptions that can be caught the traditional way do not exist. While exceptions exist in the form of panics, they can only be recovered from within deferred functions:

func f() {
  defer func() {
    if r := recover(); r != nil {
      log.Println("Recovered in f", r)
    }
  }()
  doSomethingDangerous()
}

Reporting to Sentry

Go Traceback

Now that you understand the difference between an error and a panic, let’s talk about how we can feed those into Sentry.

We first need to initialize our Raven client with the DSN for our project:

import "github.com/getsentry/raven-go"

func init() {
  raven.SetDSN("<your dsn>")
}

Now we can easily capture errors from anywhere in our application with raven.CaptureError or raven.CaptureErrorAndWait. The difference being, one is fire and forget, and the other blocks waiting for message to be sent.

fp, err := os.Open("filename.ext")
if err != nil {
  // We explicitly want to wait here because we're exiting
  // immediately after
  raven.CaptureErrorAndWait(err, nil)
  log.Fatal(err)
}

If you suspect that a function call may potentially panic, or you want to be overly cautious, you can wrap the function call in raven.CapturePanic which will implement the standard defer/recover pattern and log the panic and continue on.

func doStuff() {
  // Some stuff that we want to guard
}

// Make sure that the call to doStuff doesn't leak a panic
raven.CapturePanic(doStuff, nil)

Capturing Metadata

One of the most important aspects of Sentry is its ability to collect additional metadata about errors. In Go this is no different, but the APIs are fairly different than what you might see with other integrations. Raven clients treat metadata as a context object requiring an explicit raven.Set{User,Http,Tags}Context() and ultimately a raven.ClearContext() to reset it back. This works great for http request middlewares, for example.

Users

Sentry is great for capturing user data to attach to the errors being logged. We can do this through raven.SetUserContext and the User will get added to the context being sent along to all future errors.

raven.SetUserContext(&raven.User{ID: "123", Email: "matt@example.com"})

// Later on, call CaptureError and pass along the User
raven.CaptureError(err, nil)

// Now clear it for subsequent calls
raven.ClearContext()

Http Requests

When working with an http server, it’s extremely valuable to capture information about the request that triggered the error. To do that, Raven provides a convenient way to construct an *raven.Http interface to be appended to the current context, raven.NewHttp which takes a single argument, *http.Request. This will provide information about URL, headers, Cookies, the request method, requests IP address, etc.

raven.SetHttpContext(raven.NewHttp(req))

We also provide a hook into the stdlib’s net/http package that can wrap your handler:

func root(w http.ResponseWriter, r *http.Request) {
  // ... do stuff
}
http.HandleFunc("/", raven.RecoveryHandler(root))

Tags

Tags can be used to filter for specific errors within Sentry, and they are easy to attach with Context as well with raven.SetTagsContext(). Tags can also be set at the time of capturing the error, and they will get merged with the context, if there is any.

raven.SetTagsContext(map[string]string{"browser": "firefox"})

raven.CaptureError(err, map[string]string{"age": "20"})

In this example, you’d get tags that combined browser and age both in Sentry.

Learn More

Take a look at the raven-go project on GitHub to learn more about how things are implemented, as well as additional details on using it with Sentry.