Go

GoのコードでSentryにメッセージを送信するやり方

普通のGoのコード

GoでSentryにエラーを送信する場合、 https://github.com/getsentry/sentry-go を使う。 以前はraven-goという名前だったので、古いページではこちらで記載されているかも。

Webフレームワーク等を使わないプレーンなGoのコードの場合、 github.com/getsentry/sentry-go をimportして呼び出せばよい

Go | Sentry Documentation

package main
 
import (
	"log"
	"time"
 
	"github.com/getsentry/sentry-go"
)
 
func main() {
	err := sentry.Init(sentry.ClientOptions{
		Dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",
		// Enable printing of SDK debug messages.
		// Useful when getting started or trying to figure something out.
		Debug: true,
	})
	if err != nil {
		log.Fatalf("sentry.Init: %s", 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)
 
    sentry.CaptureMessage("It works!")
}

Scope, Hub

Scopes and Hubs for Go | Sentry Documentation

イベントがキャプチャされてSentryに送信されるとき、SDKで現在のスコープ内でイベントデータに追加情報を付与する

init() が呼ばれるとhubが作成されて、その上に空のscopeとクライアントが作成される。 scope は、Sentryにイベント送信時に contextbreadcrumbs といった情報を追加して送信する。 親scopeから継承したデータを送信する。

Stacktraceを表示したい

[Go]Sentryに対応したcustom errorの作り方

httpサーバーにSentryを組み込む

net/http | Sentry Documentation Echo | Sentry Documentation Gin | Sentry Documentation

各フレームワーク用にライブラリが用意されている。 middlewareに設定することで、handlerでpanic発生時にイベントを送信できる。

net/http の例

import (
	"fmt"
	"net/http"
 
	"github.com/getsentry/sentry-go"
	sentryhttp "github.com/getsentry/sentry-go/http"
)
 
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)
	}
}
 
func main() {
 
    // To initialize Sentry's handler, you need to initialize Sentry itself beforehand
    if err := sentry.Init(sentry.ClientOptions{
    	Dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",
    	EnableTracing: true,
    	// Set TracesSampleRate to 1.0 to capture 100%
    	// of transactions for performance monitoring.
    	// We recommend adjusting this value in production,
    	TracesSampleRate: 1.0,
    }); err != nil {
    	fmt.Printf("Sentry initialization failed: %v\n", err)
    }
    
    sentryHandler := sentryhttp.New(sentryhttp.Options{
    	Repanic: true, // Sentryに送信したあと再度panicを発生させて上位階層でキャッチできるようにする
    })
    
    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)
    }
}

middlewareで sentry.GetHubFromContext(r.Context()) を呼ぶことで、リクエストスコープでhubの設定をして他のリクエストと混ざらないようになっている。

sentryhttp.New の中では次のような処理をしている

func (h *Handler) handle(handler http.Handler) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()
		hub := sentry.GetHubFromContext(ctx)
		if hub == nil {
			hub = sentry.CurrentHub().Clone()
			ctx = sentry.SetHubOnContext(ctx, hub)
		}
		options := []sentry.SpanOption{
			sentry.OpName("http.server"),
			sentry.ContinueFromRequest(r),
			sentry.TransctionSource(sentry.SourceURL),
		}
		// We don't mind getting an existing transaction back so we don't need to
		// check if it is.
		transaction := sentry.StartTransaction(ctx,
			fmt.Sprintf("%s %s", r.Method, r.URL.Path),
			options...,
		)
		defer transaction.Finish()
		// TODO(tracing): if the next handler.ServeHTTP panics, store
		// information on the transaction accordingly (status, tag,
		// level?, ...).
		r = r.WithContext(transaction.Context())
		hub.Scope().SetRequest(r)
		defer h.recoverWithSentry(hub, r)
		// TODO(tracing): use custom response writer to intercept
		// response. Use HTTP status to add tag to transaction; set span
		// status.
		handler.ServeHTTP(w, r)
	}
}
  • http.RequestのContextからhubを取得
    • なければ生成してContextにセット
  • scopeにhttp.RequestをSetRequest
  • handlerでpanicが発生したときにrecoverする
    • Repanic: true なら再度panicを発生させる

停止時の処理

Shutdown and Draining for Echo | Sentry Documentation

サーバー停止時に sentry.Flush を呼んで、送信途中のイベントがあったら送信されるようにする。

http.Request.HeaderをSentryに送信する

0.15.0 で入った変更でデフォルトではheaderが送信されないようになった。 https://github.com/getsentry/sentry-go/pull/485 Hostのみが送信される。

sentry.Init 時に SendDefaultPII: true をつけることで、headerも送信されるようになる。

	if err := sentry.Init(sentry.ClientOptions{
		Dsn:            conf.Sentry.Dsn,
		SendDefaultPII: true,
	}); err != nil {
		log.Fatalf("Sentry initialization failed: %v\n", err)
	}

0.16.0 で、プライベートな情報以外のヘッダーは送信されるよう修正した(変更者:私)