Go: gospan で span を SQLite に溜め、「どこが遅いか」を SQL で探す
collector もエージェントも立てずに、1 プロセス内の処理時間を span として SQLite ファイルに書き出すライブラリ。pure Go なので CGO も要らない。開発中や不具合調査で「どの子処理が遅いか」を後から SQL で調べたいときに使う。
どんな時に使う?
- ログに経過時間は出ているが、どの DB 呼び出しや外部呼び出しが遅いのか分からない
- OpenTelemetry と収集基盤を入れるほどの規模ではない
- 調査用に、実行ごとのトレースをファイルで残しておきたい
やり方
go
import (
"github.com/akmadian/gospan"
"github.com/akmadian/gospan/sqlite"
)
sink, err := sqlite.New("traces", sqlite.WithName("run.sqlite", true)) // 既定は実行ごとに別ファイル
if err != nil { log.Fatal(err) }
tracer, err := gospan.New(sink)
if err != nil { log.Fatal(err) }
gospan.SetDefault(tracer) // これを呼ぶまで gospan.Start は何もしない
defer tracer.Close(context.Background())
// ミドルウェア: span 名は低カーディナリティに、パスや ID は属性へ
ctx, span := gospan.Start(r.Context(), "http.request",
slog.String("http.route", route), slog.String("http.path", r.URL.Path))
defer span.End()
// 子処理(ctx を渡すと親子になる)
ctx, q := gospan.Start(ctx, "db.query", slog.String("db.table", "users"))
defer q.End()
q.SetAttrs(slog.Int("rows", n)) // 途中で分かった値を後から追加
// 失敗時は q.Fail(err)
defer gospan.Track(ctx, "cache.lookup")() // 1 行で書ける葉 span(子は作れない)実行後は spans_named ビュー(名前と duration_ns を結合済み)や attrs テーブルを SQL で引く。
sql
SELECT name, ROUND(duration_ns / 1e6, 1) AS ms, status
FROM spans_named ORDER BY duration_ns DESC LIMIT 10;slog に流す gospan.SlogSink(logger) もあり、終わった span が 1 行のログになる。
注意点
tracer.Stats().Droppedを確認する。既定ではバッファ(8192 件)が溢れると、イベントを捨てて件数だけ数える。取りこぼしたくないならWithBlockingPolicy()を使う(その代わり呼び出し側が待たされる)。- プロセスをまたぐ context の伝播はない。サービス間の分散トレースが必要なら OpenTelemetry を使う。
Endしなかった span は書き出されない。強制終了すると、最後の Flush(1 秒ごと)以降の分は失われる。- v0.1 時点の pre-1.0。ファイル形式は固定だが、Go の API は変わる可能性がある。go.mod は
go 1.25以上が必要。 - nil の tracer や span のメソッドは何もしないので、計装はコードに残したまま、環境変数で tracer を作るかどうかだけ切り替えられる。
出典
- @matsuu · 2026-10-18
- 記事「トレーシング基盤なしで『どこが遅いか』を SQL で探す」(Zenn): zenn.dev