概要
Sentry関連で、これもついでにメモ。
インストールとか基本的な使い方については以下を参照ください。
Goではゴルーチンというとても便利な非同期手段があるので
よく非同期処理を手軽に書くことになります。
上記のSentryさんをゴルーチン内で利用する場合、どのゴルーチンの処理かを判別するために
sentry.SetTag("tagname", "tagvalue")
みたいにして、タグつけておきたい場合が多いです。
ですが、非同期処理内で Sentry の スコープ設定をする場合には注意点があって
必ず *sentry.Hub を取得して、Hubに対してスコープ設定やキャプチャをしておく
って言うのがあります。このことは、公式のドキュメントにも記載されています。
上記ページで、何回もスコープという言葉が出てきます。スコープとハブについては以下で記載されています。
てことで、以下に上のページのサンプルをほぼパクリになりますが、だめなパターンと良いパターンについてメモメモ。
駄目なパターン
駄目なパターンというのは、*sentry.Hub
を使わずに直接ゴルーチン内でsentry.ConfigureScope()
している場合です。
package log_ import ( "github.com/getsentry/sentry-go" "sync" "time" ) // SentryGoroutineBad は、Goroutineの中でSentryを使う場合に「してはいけないパターン」を表しているサンプルです。 // このサンプルのように、Hubを利用せずに直接スコープを定義して処理してはいけない。 func SentryGoroutineBad() error { // ---------------------------------------------------------------- // sentry-go における goroutine 内での利用方法について // // Goroutine内で利用する場合のやり方について以下のページにコード付きで // 記載されている。 // https://docs.sentry.io/platforms/go/goroutines/ // Goroutine内で、スコープを構成する場合は必ず *sentry.Hub を取得して // Hubに対して、スコープを構成、および、データのキャプチャを行うようにする。 // // 以下は、わざとHubを利用せずにスコープを構成してメッセージをキャプチャ // しているサンプルである。実行すると、メッセージ自体はちゃんと届くが // 設定されているタグの値が高確率でおかしくなる。 // // REFERENCES:: // https://docs.sentry.io/platforms/go/goroutines/ // https://docs.sentry.io/enriching-error-data/scopes/?platform=go // ---------------------------------------------------------------- err := sentry.Init(sentry.ClientOptions{}) if err != nil { return err } defer sentry.Flush(5 * time.Second) wg := sync.WaitGroup{} wg.Add(2) go func() { defer wg.Done() // Hubを使わずに直接Goroutine内でスコープを編成してはいけない // 他のGoroutineで同じようにスコープを編成していたりするとデータ競合が // 発生して設定が上書きされてしまう可能性がある. sentry.ConfigureScope(func(s *sentry.Scope) { s.SetTag("sentry-goroutine-example-bad", "go#1") }) for i := 0; i < 3; i++ { sentry.CaptureMessage("sentry.Hubを使わずにメッセージ送信 from Goroutine#1") } }() go func() { defer wg.Done() sentry.ConfigureScope(func(s *sentry.Scope) { s.SetTag("sentry-goroutine-example-bad", "go#2") }) for i := 0; i < 3; i++ { sentry.CaptureMessage("sentry.Hubを使わずにメッセージ送信 from Goroutine#2") } }() wg.Wait() // ------------------------------------------- // このサンプルを実行してSentryに届いた // 情報を確認すると、高確率で 2つ目の Goroutine の // sentry-goroutine-example-bad タグの値が go#1 となる // 本来であれば、 go#2 とならないといけないが // Hubを利用せずにスコープを構成しているため // データが上書きされてしまっている。 // ------------------------------------------- return nil }
上のサンプルでは、2つのゴルーチンを動かして、一つはタグの値をgo#1
にて、もう片方はgo#2
にしています。
んで、それぞれのゴルーチンごとにメッセージを3回キャプチャしてSentryにおくっています。
理屈上では、Sentryにてそれぞれのタグ値ごとにフィルタリングすると3つずつになるはずですが
以下のようになります。
まず、"sentry.Hubを使わずにメッセージ送信 from Goroutine#1"
ってメッセージの分
これはオッケイですね。メッセージとタグ値がちゃんと一致している。
では、次に"sentry.Hubを使わずにメッセージ送信 from Goroutine#2"
ってメッセージの分
紐づくタグの値がおかしいです。go#1
になってる。go#2
じゃないといけない。
てことで、Hub使わないとやっぱりおかしくなりますね。
良いパターン
以下、ちゃんとゴルーチン内では Hub を利用して設定をしているバージョンです。
package log_ import ( "github.com/getsentry/sentry-go" "sync" "time" ) // SentryGoroutineGood は、Goroutineの中でSentryを使う場合に「こうするべきパターン」を表しているサンプルです。 // このサンプルのように、Hubを利用してスコープを構成しないといけない。 func SentryGoroutineGood() error { // ---------------------------------------------------------------- // sentry-go における goroutine 内での利用方法について // // Goroutine内で利用する場合のやり方について以下のページにコード付きで // 記載されている。 // https://docs.sentry.io/platforms/go/goroutines/ // Goroutine内で、スコープを構成する場合は必ず *sentry.Hub を取得して // Hubに対して、スコープを構成、および、データのキャプチャを行うようにする。 // // REFERENCES:: // https://docs.sentry.io/platforms/go/goroutines/ // https://docs.sentry.io/enriching-error-data/scopes/?platform=go // ---------------------------------------------------------------- err := sentry.Init(sentry.ClientOptions{}) if err != nil { return err } defer sentry.Flush(5 * time.Second) wg := sync.WaitGroup{} wg.Add(2) // 以下の2つのGoroutineは、それぞれ異なるタイミングでHubを取得しているが // どちらも正しい方法となっているため、好きな方を使えば良い。 // (https://docs.sentry.io/platforms/go/goroutines/ 参照) // // 大事なのは、Goroutine内では必ずHub経由でSentryにアクセスすること。 hub := sentry.CurrentHub().Clone() go func(h *sentry.Hub) { defer wg.Done() h.ConfigureScope(func(s *sentry.Scope) { s.SetTag("sentry-goroutine-example-good", "go#1") }) for i := 0; i < 3; i++ { h.CaptureMessage("sentry.Hubを使ってメッセージ送信 from Goroutine#1") } }(hub) go func() { wg.Done() h := sentry.CurrentHub().Clone() h.ConfigureScope(func(s *sentry.Scope) { s.SetTag("sentry-goroutine-example-good", "go#2") }) for i := 0; i < 3; i++ { h.CaptureMessage("sentry.Hubを使ってメッセージ送信 from Goroutine#2") } }() wg.Wait() // ------------------------------------------- // このサンプルを実行してSentryに届いた // 情報を確認すると、正しくそれぞれのGorouineごとに // sentry-goroutine-example-good タグの値が設定されている // ------------------------------------------- return nil }
まず、"sentry.Hubを使ってメッセージ送信 from Goroutine#1"
ってメッセージの分
オッケイですね。てことで、もう一つの方。
今度はこちらもちゃんと反映されていますね。
過去の記事については、以下のページからご参照下さい。
- いろいろ備忘録日記まとめ
サンプルコードは、以下の場所で公開しています。
- いろいろ備忘録日記サンプルソース置き場