spancheck

Table of Contents

  1. 解決する問題
  2. 設定
  3. オプション
    1. チェックの種類
  4. サンプル
    1. 検出例
    2. 修正例
    3. カスタムスパン関数の登録
  5. 注意点
  6. 参考リンク

spancheck は OpenTelemetry / OpenCensus のスパンが正しく扱われているかをチェックするツールです。

解決する問題

分散トレーシングのスパンは、作成後に必ず終了(End)し、エラー時にはステータス設定やエラー記録を行う必要があります。これらのボイラープレートを忘れると、メモリリーク、トレースの欠落、障害調査時の情報不足を引き起こします。

1
2
3
4
5
6
7
8
9
10
11
12
13
// Before: スパンの終了やエラーステータスが漏れている
func ProcessOrder(ctx context.Context, orderID string) error {
ctx, span := tracer.Start(ctx, "ProcessOrder")
// span.End() がない → メモリリーク、トレース未送信

order, err := fetchOrder(ctx, orderID)
if err != nil {
// span.SetStatus も span.RecordError もない → 障害調査で見えない
return fmt.Errorf("fetch order %s: %w", orderID, err)
}

return nil
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// After: スパンを正しく扱う
func ProcessOrder(ctx context.Context, orderID string) error {
ctx, span := tracer.Start(ctx, "ProcessOrder")
defer span.End()

order, err := fetchOrder(ctx, orderID)
if err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return fmt.Errorf("fetch order %s: %w", orderID, err)
}

_ = order
return nil
}

設定

公式ドキュメント

1
2
3
4
5
6
7
8
9
10
11
12
# .golangci.yml 設定例
version: "2"

linters:
enable:
- spancheck
settings:
spancheck:
checks:
- end
- set-status
- record-error

オプション

オプション デフォルト 説明
checks []string [end] 有効にするチェック。endset-statusrecord-error を指定可能
ignore-check-signatures []string [] 指定したシグネチャの関数呼び出しがある場合、チェックを抑制する
extra-start-span-signatures []string [] カスタムのスパン開始関数を追加する

チェックの種類

チェック デフォルト 検出内容
end 有効 span.End() が呼ばれていない
set-status 無効 エラーパスで span.SetStatus(codes.Error, ...) が呼ばれていない
record-error 無効 エラーパスで span.RecordError(err) が呼ばれていない

サンプル

検出例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// end チェック: span.End() がない
func example1(ctx context.Context) error {
_, span := tracer.Start(ctx, "example1")
// spancheck: span.End is not called on all paths
return doWork(ctx)
}

// set-status チェック: エラー時にステータスが未設定
func example2(ctx context.Context) error {
ctx, span := tracer.Start(ctx, "example2")
defer span.End()

if err := doWork(ctx); err != nil {
// spancheck: span.SetStatus is not called on all error paths
return err
}
return nil
}

// record-error チェック: エラーが記録されていない
func example3(ctx context.Context) error {
ctx, span := tracer.Start(ctx, "example3")
defer span.End()

if err := doWork(ctx); err != nil {
span.SetStatus(codes.Error, err.Error())
// spancheck: span.RecordError is not called on all error paths
return err
}
return nil
}

修正例

1
2
3
4
5
6
7
8
9
10
11
func example(ctx context.Context) error {
ctx, span := tracer.Start(ctx, "example")
defer span.End()

if err := doWork(ctx); err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
return fmt.Errorf("do work: %w", err)
}
return nil
}

カスタムスパン関数の登録

1
2
3
4
5
6
7
8
9
10
11
12
# 独自のスパン開始関数をチェック対象に追加
linters:
settings:
spancheck:
checks:
- end
- set-status
- record-error
extra-start-span-signatures:
- "github.com/myorg/pkg/trace.Start:opentelemetry"
ignore-check-signatures:
- "github.com/myorg/pkg/telemetry.RecordError"

注意点

  • end チェックはデフォルトで有効。set-statusrecord-error は明示的に有効化する必要がある
  • ignore-check-signatures を使うと、ヘルパー関数内でエラー処理を一括で行っている場合に誤検知を抑制できる
  • OpenTelemetry の仕様上、スパンが End されないとバックエンドに送信されずメモリリークの原因になる
  • OpenCensus を使っている場合も同様にチェックされる

参考リンク